@venizia/ignis-docs 0.0.4-0 → 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/bootstrapping.md +4 -3
- package/wiki/references/base/components.md +47 -29
- package/wiki/references/base/controllers.md +220 -24
- package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
- package/wiki/references/base/middlewares.md +37 -3
- package/wiki/references/base/models.md +40 -2
- package/wiki/references/base/providers.md +1 -2
- package/wiki/references/base/repositories/index.md +3 -1
- package/wiki/references/base/services.md +2 -2
- package/wiki/references/components/authentication.md +261 -247
- package/wiki/references/helpers/index.md +1 -1
- package/wiki/references/helpers/socket-io.md +1 -1
- package/wiki/references/quick-reference.md +2 -2
- package/wiki/references/src-details/core.md +1 -1
- package/wiki/references/utilities/statuses.md +4 -4
- package/wiki/best-practices/code-style-standards.md +0 -1193
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Control Flow & Organization
|
|
2
|
+
|
|
3
|
+
Guidelines for control flow, logging, error handling, and code organization.
|
|
4
|
+
|
|
5
|
+
## Control Flow Patterns
|
|
6
|
+
|
|
7
|
+
### Mandatory Braces
|
|
8
|
+
|
|
9
|
+
**Always use braces for `if`, `for`, `while`, and `do-while` statements**, even for single-line bodies. Never use inline statements.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// ✅ GOOD - Always use braces
|
|
13
|
+
if (condition) {
|
|
14
|
+
doSomething();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const item of items) {
|
|
18
|
+
process(item);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
while (running) {
|
|
22
|
+
tick();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
do {
|
|
26
|
+
attempt();
|
|
27
|
+
} while (retrying);
|
|
28
|
+
|
|
29
|
+
// ❌ BAD - Never inline without braces
|
|
30
|
+
if (condition) doSomething();
|
|
31
|
+
for (const item of items) process(item);
|
|
32
|
+
while (running) tick();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Why braces are mandatory:**
|
|
36
|
+
- Prevents bugs when adding statements later
|
|
37
|
+
- Clearer code structure at a glance
|
|
38
|
+
- Consistent formatting across codebase
|
|
39
|
+
|
|
40
|
+
### Switch Statement Requirements
|
|
41
|
+
|
|
42
|
+
**All switch statements must:**
|
|
43
|
+
1. Use braces `{}` for each case block
|
|
44
|
+
2. Include a `default` case (even if it throws)
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// ✅ GOOD - Braces and default case
|
|
48
|
+
switch (status) {
|
|
49
|
+
case 'active': {
|
|
50
|
+
activateUser();
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'inactive': {
|
|
54
|
+
deactivateUser();
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'pending': {
|
|
58
|
+
notifyAdmin();
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
default: {
|
|
62
|
+
throw getError({
|
|
63
|
+
statusCode: HTTP.ResultCodes.RS_4.BadRequest,
|
|
64
|
+
message: `Unknown status: ${status}`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ❌ BAD - Missing braces and default case
|
|
70
|
+
switch (status) {
|
|
71
|
+
case 'active':
|
|
72
|
+
activateUser();
|
|
73
|
+
break;
|
|
74
|
+
case 'inactive':
|
|
75
|
+
deactivateUser();
|
|
76
|
+
break;
|
|
77
|
+
// Missing default case!
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Why these rules:**
|
|
82
|
+
- Braces prevent variable scoping issues between cases
|
|
83
|
+
- Default case ensures all values are handled
|
|
84
|
+
- Throwing in default catches unexpected values early
|
|
85
|
+
|
|
86
|
+
## Logging Patterns
|
|
87
|
+
|
|
88
|
+
### Method Context Prefix
|
|
89
|
+
|
|
90
|
+
Always include class and method context in log messages:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Format: [ClassName][methodName] Message with %s placeholders
|
|
94
|
+
this.logger.info('[binding] Asset storage bound | Key: %s | Type: %s', key, storageType);
|
|
95
|
+
this.logger.debug('[authenticate] Token validated | User: %s', userId);
|
|
96
|
+
this.logger.warn('[register] Skipping duplicate registration | Type: %s', opts.type);
|
|
97
|
+
this.logger.error('[generate] Token generation failed | Error: %s', error.message);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Structured Data
|
|
101
|
+
|
|
102
|
+
Use format specifiers for structured logging:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// %s - string, %d - number, %j - JSON object
|
|
106
|
+
this.logger.info('[create] User created | ID: %s | Email: %s', user.id, user.email);
|
|
107
|
+
this.logger.debug('[config] Server options: %j', this.serverOptions);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Log Levels
|
|
111
|
+
|
|
112
|
+
| Level | Use For |
|
|
113
|
+
|-------|---------|
|
|
114
|
+
| `error` | Exceptions that need attention |
|
|
115
|
+
| `warn` | Recoverable issues, deprecations |
|
|
116
|
+
| `info` | Important business events |
|
|
117
|
+
| `debug` | Detailed debugging information |
|
|
118
|
+
|
|
119
|
+
## Standardized Error Handling
|
|
120
|
+
|
|
121
|
+
Use the `getError` helper and `HTTP` constants to throw consistent, formatted exceptions.
|
|
122
|
+
|
|
123
|
+
### Basic Error
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { getError, HTTP } from '@venizia/ignis';
|
|
127
|
+
|
|
128
|
+
if (!record) {
|
|
129
|
+
throw getError({
|
|
130
|
+
statusCode: HTTP.ResultCodes.RS_4.NotFound,
|
|
131
|
+
message: 'Record not found',
|
|
132
|
+
details: { id: requestedId },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Error with Context
|
|
138
|
+
|
|
139
|
+
Include class/method context in error messages:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Format: [ClassName][methodName] Descriptive message
|
|
143
|
+
throw getError({
|
|
144
|
+
statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
|
|
145
|
+
message: '[JWTTokenService][generate] Failed to generate token',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
throw getError({
|
|
149
|
+
statusCode: HTTP.ResultCodes.RS_4.Unauthorized,
|
|
150
|
+
message: '[AuthMiddleware][authenticate] Missing authorization header',
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Validation Errors
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
constructor(options: IServiceOptions) {
|
|
158
|
+
if (!options.apiKey) {
|
|
159
|
+
throw getError({
|
|
160
|
+
statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
|
|
161
|
+
message: '[PaymentService] Missing required apiKey configuration',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### HTTP Status Code Quick Reference
|
|
168
|
+
|
|
169
|
+
| Category | Constant | Use Case |
|
|
170
|
+
|----------|----------|----------|
|
|
171
|
+
| Success | `HTTP.ResultCodes.RS_2.Ok` | Successful response |
|
|
172
|
+
| Created | `HTTP.ResultCodes.RS_2.Created` | Resource created |
|
|
173
|
+
| Bad Request | `HTTP.ResultCodes.RS_4.BadRequest` | Invalid input |
|
|
174
|
+
| Unauthorized | `HTTP.ResultCodes.RS_4.Unauthorized` | Missing/invalid auth |
|
|
175
|
+
| Forbidden | `HTTP.ResultCodes.RS_4.Forbidden` | Insufficient permissions |
|
|
176
|
+
| Not Found | `HTTP.ResultCodes.RS_4.NotFound` | Resource not found |
|
|
177
|
+
| Internal Error | `HTTP.ResultCodes.RS_5.InternalServerError` | Server errors |
|
|
178
|
+
|
|
179
|
+
## Code Organization
|
|
180
|
+
|
|
181
|
+
### Section Separator Comments
|
|
182
|
+
|
|
183
|
+
Use visual separators for major code sections in long files:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Type Definitions
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
type TMyType = { /* ... */ };
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Constants
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
const DEFAULT_OPTIONS = { /* ... */ };
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Main Implementation
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
export class MyClass {
|
|
203
|
+
// ...
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Guidelines:**
|
|
208
|
+
- Use for files > 200 lines with distinct sections
|
|
209
|
+
- Use 75-character wide separator lines
|
|
210
|
+
- Descriptive section names (2-4 words)
|
|
211
|
+
|
|
212
|
+
### Import Organization Order
|
|
213
|
+
|
|
214
|
+
Organize imports in this order:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// 1. Node built-ins (with 'node:' prefix)
|
|
218
|
+
import fs from 'node:fs';
|
|
219
|
+
import path from 'node:path';
|
|
220
|
+
|
|
221
|
+
// 2. Third-party packages (alphabetical)
|
|
222
|
+
import { z } from '@hono/zod-openapi';
|
|
223
|
+
import dayjs from 'dayjs';
|
|
224
|
+
|
|
225
|
+
// 3. Internal absolute imports (by domain/package)
|
|
226
|
+
import { getError } from '@venizia/ignis-helpers';
|
|
227
|
+
import { BaseEntity } from '@/base/models';
|
|
228
|
+
import { UserService } from '@/services';
|
|
229
|
+
|
|
230
|
+
// 4. Relative imports (same feature) - LAST
|
|
231
|
+
import { AbstractRepository } from './base';
|
|
232
|
+
import { QueryBuilder } from '../query';
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Rules:**
|
|
236
|
+
- Blank line between each group
|
|
237
|
+
- Alphabetical within each group
|
|
238
|
+
- `node:` prefix for Node.js built-ins
|
|
239
|
+
- Relative imports only for same feature/module
|
|
240
|
+
|
|
241
|
+
## See Also
|
|
242
|
+
|
|
243
|
+
- [Error Handling](../error-handling) - Comprehensive error patterns
|
|
244
|
+
- [Logging Reference](../../references/helpers/logger) - Logger API
|
|
245
|
+
- [Function Patterns](./function-patterns) - Method organization
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Documentation (JSDoc)
|
|
2
|
+
|
|
3
|
+
Use JSDoc comments for public APIs to improve IDE support and generate documentation.
|
|
4
|
+
|
|
5
|
+
## When to Use JSDoc
|
|
6
|
+
|
|
7
|
+
| Context | Required? | Reason |
|
|
8
|
+
|---------|-----------|--------|
|
|
9
|
+
| Public methods | Yes | Consumers need documentation |
|
|
10
|
+
| Exported functions | Yes | API contract documentation |
|
|
11
|
+
| Complex types | Yes | Clarify usage |
|
|
12
|
+
| Private methods | No | Internal, can change |
|
|
13
|
+
| Self-explanatory code | No | Avoid redundant docs |
|
|
14
|
+
|
|
15
|
+
## JSDoc Format
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
/**
|
|
19
|
+
* Brief description of what the function does.
|
|
20
|
+
*
|
|
21
|
+
* @param opts - Description of the options object
|
|
22
|
+
* @param opts.filter - Query filter with where, limit, offset
|
|
23
|
+
* @param opts.options - Additional options like transaction
|
|
24
|
+
* @returns Promise resolving to the query result
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const users = await userRepo.find({
|
|
28
|
+
* filter: { where: { status: 'ACTIVE' }, limit: 10 },
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* @throws {ApplicationError} When validation fails
|
|
32
|
+
* @see {@link UserService} for business logic
|
|
33
|
+
*/
|
|
34
|
+
async find(opts: TFindOptions): Promise<TFindResult<TUser>> {
|
|
35
|
+
// implementation
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Common JSDoc Tags
|
|
40
|
+
|
|
41
|
+
| Tag | Usage |
|
|
42
|
+
|-----|-------|
|
|
43
|
+
| `@param` | Document function parameters |
|
|
44
|
+
| `@returns` | Document return value |
|
|
45
|
+
| `@throws` | Document thrown exceptions |
|
|
46
|
+
| `@example` | Provide usage examples |
|
|
47
|
+
| `@see` | Reference related items |
|
|
48
|
+
| `@deprecated` | Mark as deprecated with migration path |
|
|
49
|
+
| `@internal` | Mark as internal (not public API) |
|
|
50
|
+
| `@since` | Version when feature was added |
|
|
51
|
+
| `@default` | Default value for optional parameter |
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
### Service Method
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new user account with the given data.
|
|
60
|
+
*
|
|
61
|
+
* Validates that the email is unique, hashes the password,
|
|
62
|
+
* and sends a welcome email upon successful creation.
|
|
63
|
+
*
|
|
64
|
+
* @param data - User creation data
|
|
65
|
+
* @returns The created user without sensitive fields
|
|
66
|
+
* @throws {ApplicationError} 409 if email already exists
|
|
67
|
+
* @throws {ApplicationError} 422 if validation fails
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* const user = await userService.createUser({
|
|
71
|
+
* email: 'john@example.com',
|
|
72
|
+
* name: 'John Doe',
|
|
73
|
+
* password: 'SecurePass123!',
|
|
74
|
+
* });
|
|
75
|
+
*/
|
|
76
|
+
async createUser(data: TCreateUserRequest): Promise<TUser> {
|
|
77
|
+
// ...
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Repository Method
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
/**
|
|
85
|
+
* Finds entities matching the given filter.
|
|
86
|
+
*
|
|
87
|
+
* @param opts - Find options
|
|
88
|
+
* @param opts.filter - Query filter
|
|
89
|
+
* @param opts.filter.where - Conditions to match
|
|
90
|
+
* @param opts.filter.limit - Maximum records to return (default: 100)
|
|
91
|
+
* @param opts.filter.offset - Records to skip for pagination
|
|
92
|
+
* @param opts.filter.order - Sort order (e.g., ['createdAt DESC'])
|
|
93
|
+
* @param opts.filter.include - Relations to load
|
|
94
|
+
* @returns Promise with data array and count
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Find active users, sorted by name
|
|
98
|
+
* const result = await userRepo.find({
|
|
99
|
+
* filter: {
|
|
100
|
+
* where: { status: 'ACTIVE' },
|
|
101
|
+
* order: ['name ASC'],
|
|
102
|
+
* limit: 20,
|
|
103
|
+
* },
|
|
104
|
+
* });
|
|
105
|
+
*/
|
|
106
|
+
async find(opts: TFindOpts<Schema>): Promise<TFindResult<TEntity>> {
|
|
107
|
+
// ...
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Deprecation
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated Use {@link findById} instead. Will be removed in v2.0.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* // Before (deprecated)
|
|
119
|
+
* const user = await repo.getById('123');
|
|
120
|
+
*
|
|
121
|
+
* // After (recommended)
|
|
122
|
+
* const { data: user } = await repo.findById({ id: '123' });
|
|
123
|
+
*/
|
|
124
|
+
async getById(id: string): Promise<TUser | null> {
|
|
125
|
+
return this.findById({ id }).then(r => r.data);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Type Documentation
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
/**
|
|
133
|
+
* Options for configuring user audit columns.
|
|
134
|
+
*
|
|
135
|
+
* @property dataType - The database type for user IDs ('string' | 'number')
|
|
136
|
+
* @property columnName - The column name in the database
|
|
137
|
+
* @property allowAnonymous - Whether to allow null user IDs (default: true)
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const opts: TUserAuditColumnOpts = {
|
|
141
|
+
* dataType: 'string',
|
|
142
|
+
* columnName: 'created_by',
|
|
143
|
+
* allowAnonymous: false,
|
|
144
|
+
* };
|
|
145
|
+
*/
|
|
146
|
+
type TUserAuditColumnOpts = {
|
|
147
|
+
dataType: 'string' | 'number';
|
|
148
|
+
columnName: string;
|
|
149
|
+
allowAnonymous?: boolean;
|
|
150
|
+
};
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Interface Documentation
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
/**
|
|
157
|
+
* Configuration for JWT authentication strategy.
|
|
158
|
+
*
|
|
159
|
+
* @interface IJWTStrategyOptions
|
|
160
|
+
* @property secret - Secret key for signing tokens (required)
|
|
161
|
+
* @property expiresIn - Token expiration time (default: '1h')
|
|
162
|
+
* @property algorithm - Signing algorithm (default: 'HS256')
|
|
163
|
+
* @property issuer - Token issuer claim
|
|
164
|
+
* @property audience - Token audience claim
|
|
165
|
+
*/
|
|
166
|
+
interface IJWTStrategyOptions {
|
|
167
|
+
secret: string;
|
|
168
|
+
expiresIn?: string;
|
|
169
|
+
algorithm?: 'HS256' | 'HS384' | 'HS512';
|
|
170
|
+
issuer?: string;
|
|
171
|
+
audience?: string;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Class Documentation
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
/**
|
|
179
|
+
* Base repository providing CRUD operations for database entities.
|
|
180
|
+
*
|
|
181
|
+
* Extends this class to create entity-specific repositories with
|
|
182
|
+
* type-safe operations and automatic schema binding.
|
|
183
|
+
*
|
|
184
|
+
* @template Schema - The Drizzle table schema type
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* @repository({ model: User, dataSource: PostgresDataSource })
|
|
188
|
+
* export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
189
|
+
* // Custom methods here
|
|
190
|
+
* }
|
|
191
|
+
*
|
|
192
|
+
* @see {@link BaseEntity} for model definition
|
|
193
|
+
* @see {@link BaseDataSource} for database connection
|
|
194
|
+
*/
|
|
195
|
+
abstract class DefaultCRUDRepository<Schema extends TTableSchemaWithId> {
|
|
196
|
+
// ...
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Best Practices
|
|
201
|
+
|
|
202
|
+
### Do
|
|
203
|
+
|
|
204
|
+
- Write descriptions in third person ("Creates...", "Returns...", "Validates...")
|
|
205
|
+
- Include `@example` for non-obvious usage
|
|
206
|
+
- Document all parameters with `@param`
|
|
207
|
+
- Use `@throws` for expected exceptions
|
|
208
|
+
- Link to related items with `@see` and `{@link}`
|
|
209
|
+
|
|
210
|
+
### Don't
|
|
211
|
+
|
|
212
|
+
- Don't document obvious things (`@param id - The ID` - unhelpful)
|
|
213
|
+
- Don't copy TypeScript types into JSDoc (they're already visible)
|
|
214
|
+
- Don't write multi-paragraph descriptions for simple functions
|
|
215
|
+
- Don't use JSDoc for private implementation details
|
|
216
|
+
|
|
217
|
+
## See Also
|
|
218
|
+
|
|
219
|
+
- [Type Safety](./type-safety) - TypeScript best practices
|
|
220
|
+
- [Function Patterns](./function-patterns) - Method organization
|
|
221
|
+
- [API Reference](../../references/) - Documentation examples
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Function Patterns
|
|
2
|
+
|
|
3
|
+
Consistent function patterns improve code readability and maintainability.
|
|
4
|
+
|
|
5
|
+
## Module Exports
|
|
6
|
+
|
|
7
|
+
### Prefer Named Exports
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
**Why?**
|
|
12
|
+
- **Refactoring:** Renaming a symbol automatically updates imports across the monorepo
|
|
13
|
+
- **Consistency:** Enforces consistent naming across all files importing the module
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// ✅ GOOD
|
|
17
|
+
export class UserController { }
|
|
18
|
+
export function createUser() { }
|
|
19
|
+
export const DEFAULT_OPTIONS = { };
|
|
20
|
+
|
|
21
|
+
// ❌ BAD
|
|
22
|
+
export default class UserController { }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## The Options Object Pattern
|
|
26
|
+
|
|
27
|
+
Prefer using a single object parameter (`opts`) over multiple positional arguments, especially for constructors and public methods with more than 2 arguments.
|
|
28
|
+
|
|
29
|
+
**Why?**
|
|
30
|
+
- **Extensibility:** You can add new properties without breaking existing calls
|
|
31
|
+
- **Readability:** Named keys act as documentation at the call site
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// ✅ GOOD
|
|
35
|
+
class UserService {
|
|
36
|
+
createUser(opts: { name: string; email: string; role?: string }) {
|
|
37
|
+
// ...
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Usage: service.createUser({ name: 'John', email: 'john@example.com' });
|
|
41
|
+
|
|
42
|
+
// ❌ BAD
|
|
43
|
+
class UserService {
|
|
44
|
+
createUser(name: string, email: string, role?: string) {
|
|
45
|
+
// ...
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Usage: service.createUser('John', 'john@example.com');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Function Naming Conventions
|
|
52
|
+
|
|
53
|
+
Use consistent prefixes based on function purpose:
|
|
54
|
+
|
|
55
|
+
| Prefix | Purpose | Examples |
|
|
56
|
+
|--------|---------|----------|
|
|
57
|
+
| `generate*` | Create column definitions / schemas | `generateIdColumnDefs()`, `generateTzColumnDefs()` |
|
|
58
|
+
| `build*` | Construct complex objects | `buildPrimitiveCondition()`, `buildJsonOrderBy()` |
|
|
59
|
+
| `to*` | Convert/transform data | `toCamel()`, `toBoolean()`, `toStringDecimal()` |
|
|
60
|
+
| `is*` | Boolean validation/check | `isWeekday()`, `isInt()`, `isFloat()`, `isPromiseLike()` |
|
|
61
|
+
| `extract*` | Pull out specific parts | `extractTimestamp()`, `extractWorkerId()`, `extractSequence()` |
|
|
62
|
+
| `enrich*` | Enhance with additional data | `enrichUserAudit()`, `enrichWithMetadata()` |
|
|
63
|
+
| `get*` | Retrieve/fetch data | `getSchema()`, `getConnector()`, `getError()` |
|
|
64
|
+
| `resolve*` | Determine/compute value | `resolveValue()`, `resolvePath()` |
|
|
65
|
+
|
|
66
|
+
**Examples:**
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Generators - create schema definitions
|
|
70
|
+
const idCols = generateIdColumnDefs({ id: { dataType: 'string' } });
|
|
71
|
+
const tzCols = generateTzColumnDefs();
|
|
72
|
+
|
|
73
|
+
// Builders - construct complex query objects
|
|
74
|
+
const condition = buildPrimitiveCondition(column, operator, value);
|
|
75
|
+
const orderBy = buildJsonOrderBy(schema, path, direction);
|
|
76
|
+
|
|
77
|
+
// Converters - transform data types
|
|
78
|
+
const camelCase = toCamel('snake_case');
|
|
79
|
+
const bool = toBoolean('true');
|
|
80
|
+
const decimal = toStringDecimal(123.456, 2);
|
|
81
|
+
|
|
82
|
+
// Validators - boolean checks
|
|
83
|
+
if (isWeekday(date)) { /* ... */ }
|
|
84
|
+
if (isInt(value)) { /* ... */ }
|
|
85
|
+
if (isPromiseLike(result)) { /* ... */ }
|
|
86
|
+
|
|
87
|
+
// Extractors - pull specific data
|
|
88
|
+
const timestamp = extractTimestamp(snowflakeId);
|
|
89
|
+
const workerId = extractWorkerId(snowflakeId);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Scope Naming
|
|
93
|
+
|
|
94
|
+
Every class extending a base class should set its scope using `ClassName.name`:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
export class JWTTokenService extends BaseService {
|
|
98
|
+
constructor() {
|
|
99
|
+
super({ scope: JWTTokenService.name });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class UserController extends BaseController {
|
|
104
|
+
constructor() {
|
|
105
|
+
super({ scope: UserController.name });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Performance Logging Pattern
|
|
111
|
+
|
|
112
|
+
Use `performance.now()` for timing critical operations:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const t = performance.now();
|
|
116
|
+
|
|
117
|
+
// ... operation to measure ...
|
|
118
|
+
|
|
119
|
+
this.logger.info('[methodName] DONE | Took: %s (ms)', performance.now() - t);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**With the helper utility:**
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { executeWithPerformanceMeasure } from '@venizia/ignis';
|
|
126
|
+
|
|
127
|
+
await executeWithPerformanceMeasure({
|
|
128
|
+
logger: this.logger,
|
|
129
|
+
scope: 'DataSync',
|
|
130
|
+
description: 'Sync user records',
|
|
131
|
+
task: async () => {
|
|
132
|
+
await syncAllUsers();
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
// Logs: [DataSync] Sync user records | Took: 1234.56 (ms)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## See Also
|
|
139
|
+
|
|
140
|
+
- [Naming Conventions](./naming-conventions) - Class and file naming
|
|
141
|
+
- [Type Safety](./type-safety) - Typed function signatures
|
|
142
|
+
- [Route Definitions](./route-definitions) - Controller methods
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Code Style Standards
|
|
2
|
+
|
|
3
|
+
Maintain consistent code style using **Prettier** (formatting) and **ESLint** (code quality). Ignis provides centralized configurations via the `@venizia/dev-configs` package.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Aspect | Standard |
|
|
8
|
+
|--------|----------|
|
|
9
|
+
| Interface prefix | `I` (e.g., `IUserService`) |
|
|
10
|
+
| Type alias prefix | `T` (e.g., `TUserRequest`) |
|
|
11
|
+
| Class naming | PascalCase with suffix (e.g., `UserController`) |
|
|
12
|
+
| File naming | kebab-case (e.g., `user.controller.ts`) |
|
|
13
|
+
| Private fields | Underscore prefix (`_dataSource`) |
|
|
14
|
+
| Binding keys | `@app/[component]/[feature]` |
|
|
15
|
+
| Constants | Static readonly class (not enums) |
|
|
16
|
+
| Barrel exports | `index.ts` at every folder level |
|
|
17
|
+
| Error format | `[ClassName][method] Message` |
|
|
18
|
+
| Logging format | `[method] Message \| Key: %s` |
|
|
19
|
+
| Default options | `DEFAULT_OPTIONS` constant |
|
|
20
|
+
| Type safety | No `any` or `unknown` allowed |
|
|
21
|
+
| Scope naming | `ClassName.name` |
|
|
22
|
+
| Arguments | Options object (`opts`) |
|
|
23
|
+
| Exports | Named exports only |
|
|
24
|
+
| Return types | Explicitly defined |
|
|
25
|
+
| Control flow | Always use braces (`{}`) |
|
|
26
|
+
| Switch statements | Braces + default case required |
|
|
27
|
+
| Imports | Node → Third-party → Internal → Relative |
|
|
28
|
+
| Function naming | `generate*`, `build*`, `to*`, `is*`, `extract*` |
|
|
29
|
+
|
|
30
|
+
## Sections
|
|
31
|
+
|
|
32
|
+
| Section | Description |
|
|
33
|
+
|---------|-------------|
|
|
34
|
+
| [Tooling](./tooling) | ESLint, Prettier, TypeScript configuration |
|
|
35
|
+
| [Naming Conventions](./naming-conventions) | Classes, files, types, binding keys |
|
|
36
|
+
| [Type Safety](./type-safety) | Avoiding `any`, explicit returns, generics |
|
|
37
|
+
| [Function Patterns](./function-patterns) | Options object, naming, exports |
|
|
38
|
+
| [Route Definitions](./route-definitions) | Config-driven routes, OpenAPI integration |
|
|
39
|
+
| [Constants & Configuration](./constants-configuration) | Static classes vs enums, defaults |
|
|
40
|
+
| [Control Flow & Organization](./control-flow) | Braces, switches, imports, logging |
|
|
41
|
+
| [Advanced Patterns](./advanced-patterns) | Mixins, factories, value resolvers |
|
|
42
|
+
| [Documentation (JSDoc)](./documentation) | When and how to document code |
|
|
43
|
+
|
|
44
|
+
## Essential Examples
|
|
45
|
+
|
|
46
|
+
### Naming
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Interfaces use 'I' prefix
|
|
50
|
+
interface IUserService { }
|
|
51
|
+
|
|
52
|
+
// Type aliases use 'T' prefix
|
|
53
|
+
type TUserRequest = { };
|
|
54
|
+
|
|
55
|
+
// Classes use PascalCase with suffix
|
|
56
|
+
class UserController extends BaseController { }
|
|
57
|
+
class UserService extends BaseService { }
|
|
58
|
+
class UserRepository extends BaseRepository { }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### File Structure
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
src/components/auth/
|
|
65
|
+
├── index.ts # Barrel exports
|
|
66
|
+
├── component.ts # IoC binding
|
|
67
|
+
├── controller.ts # Routes
|
|
68
|
+
└── common/
|
|
69
|
+
├── index.ts
|
|
70
|
+
├── keys.ts # Binding keys
|
|
71
|
+
└── types.ts # Interfaces
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Constants (Static Class vs Enum)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// ✅ GOOD - Static class (tree-shakable)
|
|
78
|
+
export class UserStatuses {
|
|
79
|
+
static readonly ACTIVE = 'active';
|
|
80
|
+
static readonly INACTIVE = 'inactive';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ❌ AVOID - Enum (not tree-shakable)
|
|
84
|
+
enum UserStatus {
|
|
85
|
+
ACTIVE = 'active',
|
|
86
|
+
INACTIVE = 'inactive',
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Import Order
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// 1. Node built-ins
|
|
94
|
+
import fs from 'node:fs';
|
|
95
|
+
|
|
96
|
+
// 2. Third-party
|
|
97
|
+
import { z } from '@hono/zod-openapi';
|
|
98
|
+
|
|
99
|
+
// 3. Internal absolute
|
|
100
|
+
import { getError } from '@venizia/ignis-helpers';
|
|
101
|
+
|
|
102
|
+
// 4. Relative (same feature)
|
|
103
|
+
import { QueryBuilder } from './query';
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## See Also
|
|
107
|
+
|
|
108
|
+
- [Architectural Patterns](../architectural-patterns) - High-level design
|
|
109
|
+
- [Common Pitfalls](../common-pitfalls) - Mistakes to avoid
|
|
110
|
+
- [@venizia/dev-configs Reference](../../references/src-details/dev-configs) - Full tooling docs
|