@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,174 @@
|
|
|
1
|
+
# Naming Conventions
|
|
2
|
+
|
|
3
|
+
Consistent naming improves code readability and maintainability.
|
|
4
|
+
|
|
5
|
+
## Directory Structure
|
|
6
|
+
|
|
7
|
+
### Component Organization
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/components/[feature]/
|
|
11
|
+
├── index.ts # Barrel exports
|
|
12
|
+
├── component.ts # IoC binding setup
|
|
13
|
+
├── controller.ts # Route handlers
|
|
14
|
+
└── common/
|
|
15
|
+
├── index.ts # Barrel exports
|
|
16
|
+
├── keys.ts # Binding key constants
|
|
17
|
+
├── types.ts # Interfaces and types
|
|
18
|
+
└── rest-paths.ts # Route path constants
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Complex Component (with multiple features)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/components/auth/
|
|
25
|
+
├── index.ts
|
|
26
|
+
├── authenticate/
|
|
27
|
+
│ ├── index.ts
|
|
28
|
+
│ ├── component.ts
|
|
29
|
+
│ ├── common/
|
|
30
|
+
│ ├── controllers/
|
|
31
|
+
│ ├── services/
|
|
32
|
+
│ └── strategies/
|
|
33
|
+
└── models/
|
|
34
|
+
├── entities/ # Database models
|
|
35
|
+
└── requests/ # Request schemas
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Barrel Exports
|
|
39
|
+
|
|
40
|
+
Every folder should have an `index.ts` that re-exports its contents:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// components/health-check/index.ts
|
|
44
|
+
export * from './common';
|
|
45
|
+
export * from './component';
|
|
46
|
+
export * from './controller';
|
|
47
|
+
|
|
48
|
+
// components/health-check/common/index.ts
|
|
49
|
+
export * from './keys';
|
|
50
|
+
export * from './rest-paths';
|
|
51
|
+
export * from './types';
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Class Names
|
|
55
|
+
|
|
56
|
+
| Type | Pattern | Example |
|
|
57
|
+
|------|---------|---------|
|
|
58
|
+
| Components | `[Feature]Component` | `HealthCheckComponent`, `AuthComponent` |
|
|
59
|
+
| Controllers | `[Feature]Controller` | `UserController`, `AuthController` |
|
|
60
|
+
| Services | `[Feature]Service` | `JWTTokenService`, `PaymentService` |
|
|
61
|
+
| Repositories | `[Feature]Repository` | `UserRepository`, `OrderRepository` |
|
|
62
|
+
| Strategies | `[Feature]Strategy` | `JWTAuthenticationStrategy` |
|
|
63
|
+
| Factories | `[Feature]Factory` | `UIProviderFactory` |
|
|
64
|
+
|
|
65
|
+
## File Names
|
|
66
|
+
|
|
67
|
+
Both styles are acceptable: `[type].ts` or `[name].[type].ts`
|
|
68
|
+
|
|
69
|
+
| Type | Single File | Multiple Files |
|
|
70
|
+
|------|-------------|----------------|
|
|
71
|
+
| Components | `component.ts` | `auth.component.ts` |
|
|
72
|
+
| Controllers | `controller.ts` | `user.controller.ts` |
|
|
73
|
+
| Services | `service.ts` | `jwt-token.service.ts` |
|
|
74
|
+
| Repositories | `repository.ts` | `user.repository.ts` |
|
|
75
|
+
| Types/Interfaces | `types.ts` | `user.types.ts` |
|
|
76
|
+
| Constants | `constants.ts` | `keys.ts`, `rest-paths.ts` |
|
|
77
|
+
| Schemas | `schema.ts` | `sign-in.schema.ts` |
|
|
78
|
+
|
|
79
|
+
**Guidelines:**
|
|
80
|
+
- Use `[type].ts` when there's only one file of that type in the folder
|
|
81
|
+
- Use `[name].[type].ts` when there are multiple files of the same type
|
|
82
|
+
- Use kebab-case for multi-word names: `jwt-token.service.ts`
|
|
83
|
+
|
|
84
|
+
## Type and Interface Prefixes
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Interfaces use 'I' prefix
|
|
88
|
+
interface IHealthCheckOptions {
|
|
89
|
+
restOptions: { path: string };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface IAuthService {
|
|
93
|
+
signIn(context: Context): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Type aliases use 'T' prefix
|
|
97
|
+
type TSignInRequest = z.infer<typeof SignInRequestSchema>;
|
|
98
|
+
type TRouteContext = Context<Env, Path, Input>;
|
|
99
|
+
|
|
100
|
+
// Generic constraints
|
|
101
|
+
type TTableSchemaWithId = { id: PgColumn };
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Binding Keys
|
|
105
|
+
|
|
106
|
+
Use static class with `@app/[component]/[feature]` format:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
export class HealthCheckBindingKeys {
|
|
110
|
+
static readonly HEALTH_CHECK_OPTIONS = '@app/health-check/options';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class SocketIOBindingKeys {
|
|
114
|
+
static readonly SOCKET_IO_INSTANCE = '@app/socket-io/instance';
|
|
115
|
+
static readonly SERVER_OPTIONS = '@app/socket-io/server-options';
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Private Field Naming
|
|
120
|
+
|
|
121
|
+
Use underscore prefix (`_`) for private and protected class fields to distinguish them from public fields and method parameters.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
class MyRepository extends BaseRepository {
|
|
125
|
+
// Private fields with underscore prefix
|
|
126
|
+
private _dataSource: IDataSource;
|
|
127
|
+
private _entity: BaseEntity;
|
|
128
|
+
private _hiddenProperties: Set<string> | null = null;
|
|
129
|
+
|
|
130
|
+
// Protected fields also use underscore prefix
|
|
131
|
+
protected _schemaFactory?: ReturnType<typeof createSchemaFactory>;
|
|
132
|
+
|
|
133
|
+
constructor(dataSource: IDataSource) {
|
|
134
|
+
// 'dataSource' (param) vs '_dataSource' (field)
|
|
135
|
+
this._dataSource = dataSource;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Benefits:**
|
|
141
|
+
- Clear distinction between fields and parameters
|
|
142
|
+
- Avoids naming conflicts in constructors
|
|
143
|
+
- Consistent with TypeScript community conventions
|
|
144
|
+
|
|
145
|
+
## Sentinel Value Pattern for Caching
|
|
146
|
+
|
|
147
|
+
Use `null` to distinguish "not computed" from "computed as undefined" for lazy-initialized cached values.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
class Repository {
|
|
151
|
+
// null = not computed yet, undefined = computed but no value
|
|
152
|
+
private _visibleProperties: Record<string, any> | null | undefined = null;
|
|
153
|
+
|
|
154
|
+
get visibleProperties(): Record<string, any> | undefined {
|
|
155
|
+
if (this._visibleProperties !== null) {
|
|
156
|
+
return this._visibleProperties;
|
|
157
|
+
}
|
|
158
|
+
// Compute once and cache (may be undefined)
|
|
159
|
+
this._visibleProperties = this.computeVisibleProperties();
|
|
160
|
+
return this._visibleProperties;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Why not just `undefined`?**
|
|
166
|
+
- `undefined` can be a valid computed result
|
|
167
|
+
- `null` clearly indicates "never computed"
|
|
168
|
+
- Prevents redundant re-computation
|
|
169
|
+
|
|
170
|
+
## See Also
|
|
171
|
+
|
|
172
|
+
- [Type Safety](./type-safety) - Type naming and constraints
|
|
173
|
+
- [Function Patterns](./function-patterns) - Function naming conventions
|
|
174
|
+
- [Constants & Configuration](./constants-configuration) - Constant naming
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Route Definitions
|
|
2
|
+
|
|
3
|
+
Ignis supports multiple methods for defining routes. Choose based on your needs.
|
|
4
|
+
|
|
5
|
+
## Method 1: Config-Driven Routes
|
|
6
|
+
|
|
7
|
+
Define route configurations as constants with UPPER_CASE names:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// common/rest-paths.ts
|
|
11
|
+
export class UserRestPaths {
|
|
12
|
+
static readonly ROOT = '/';
|
|
13
|
+
static readonly BY_ID = '/:id';
|
|
14
|
+
static readonly PROFILE = '/profile';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// common/route-configs.ts
|
|
18
|
+
export const RouteConfigs = {
|
|
19
|
+
GET_USERS: {
|
|
20
|
+
method: HTTP.Methods.GET,
|
|
21
|
+
path: UserRestPaths.ROOT,
|
|
22
|
+
responses: jsonResponse({
|
|
23
|
+
[HTTP.ResultCodes.RS_2.Ok]: UserListSchema,
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
GET_USER_BY_ID: {
|
|
27
|
+
method: HTTP.Methods.GET,
|
|
28
|
+
path: UserRestPaths.BY_ID,
|
|
29
|
+
request: {
|
|
30
|
+
params: z.object({ id: z.string() }),
|
|
31
|
+
},
|
|
32
|
+
responses: jsonResponse({
|
|
33
|
+
[HTTP.ResultCodes.RS_2.Ok]: UserSchema,
|
|
34
|
+
[HTTP.ResultCodes.RS_4.NotFound]: ErrorSchema,
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
} as const;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Method 2: Using `@api` Decorator
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
@controller({ path: '/users' })
|
|
44
|
+
export class UserController extends BaseController {
|
|
45
|
+
|
|
46
|
+
@api({ configs: RouteConfigs.GET_USERS })
|
|
47
|
+
list(context: TRouteContext<typeof RouteConfigs.GET_USERS>) {
|
|
48
|
+
return context.json({ users: [] }, HTTP.ResultCodes.RS_2.Ok);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@api({ configs: RouteConfigs.GET_USER_BY_ID })
|
|
52
|
+
getById(context: TRouteContext<typeof RouteConfigs.GET_USER_BY_ID>) {
|
|
53
|
+
const { id } = context.req.valid('param');
|
|
54
|
+
return context.json({ id, name: 'User' }, HTTP.ResultCodes.RS_2.Ok);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Method 3: Using `bindRoute` (Programmatic)
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
@controller({ path: '/health' })
|
|
63
|
+
export class HealthCheckController extends BaseController {
|
|
64
|
+
constructor() {
|
|
65
|
+
super({ scope: HealthCheckController.name });
|
|
66
|
+
|
|
67
|
+
this.bindRoute({ configs: RouteConfigs.GET_HEALTH }).to({
|
|
68
|
+
handler: context => context.json({ status: 'ok' }),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Method 4: Using `defineRoute` (Inline)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
@controller({ path: '/health' })
|
|
78
|
+
export class HealthCheckController extends BaseController {
|
|
79
|
+
constructor() {
|
|
80
|
+
super({ scope: HealthCheckController.name });
|
|
81
|
+
|
|
82
|
+
this.defineRoute({
|
|
83
|
+
configs: RouteConfigs.POST_PING,
|
|
84
|
+
handler: context => {
|
|
85
|
+
const { message } = context.req.valid('json');
|
|
86
|
+
return context.json({ echo: message }, HTTP.ResultCodes.RS_2.Ok);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Comparison
|
|
94
|
+
|
|
95
|
+
| Method | Use Case | Pros | Cons |
|
|
96
|
+
|--------|----------|------|------|
|
|
97
|
+
| `@api` decorator | Most routes | Clean, declarative | Requires decorator support |
|
|
98
|
+
| `bindRoute` | Dynamic routes | Programmatic control | More verbose |
|
|
99
|
+
| `defineRoute` | Simple inline routes | Quick setup | Less reusable |
|
|
100
|
+
|
|
101
|
+
## OpenAPI Schema Integration
|
|
102
|
+
|
|
103
|
+
Use Zod with `.openapi()` for automatic documentation:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const CreateUserSchema = z.object({
|
|
107
|
+
email: z.string().email(),
|
|
108
|
+
name: z.string().min(1).max(100),
|
|
109
|
+
}).openapi({
|
|
110
|
+
description: 'Create user request body',
|
|
111
|
+
example: { email: 'user@example.com', name: 'John Doe' },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const UserSchema = z.object({
|
|
115
|
+
id: z.string().uuid(),
|
|
116
|
+
email: z.string().email(),
|
|
117
|
+
name: z.string(),
|
|
118
|
+
createdAt: z.string().datetime(),
|
|
119
|
+
}).openapi({
|
|
120
|
+
description: 'User response',
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Request Validation
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
export const RouteConfigs = {
|
|
128
|
+
CREATE_USER: {
|
|
129
|
+
method: HTTP.Methods.POST,
|
|
130
|
+
path: '/',
|
|
131
|
+
request: {
|
|
132
|
+
body: jsonContent({
|
|
133
|
+
schema: CreateUserSchema,
|
|
134
|
+
description: 'User data',
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
responses: jsonResponse({
|
|
138
|
+
[HTTP.ResultCodes.RS_2.Created]: UserSchema,
|
|
139
|
+
[HTTP.ResultCodes.RS_4.BadRequest]: ErrorSchema,
|
|
140
|
+
[HTTP.ResultCodes.RS_4.Conflict]: ErrorSchema,
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
} as const;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## See Also
|
|
147
|
+
|
|
148
|
+
- [API Usage Examples](../api-usage-examples) - Full API patterns
|
|
149
|
+
- [Controllers Reference](../../references/base/controllers) - Controller API
|
|
150
|
+
- [Swagger Component](../../references/components/swagger) - OpenAPI setup
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Tooling Configuration
|
|
2
|
+
|
|
3
|
+
Ignis provides centralized development configurations via the `@venizia/dev-configs` package.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add -d @venizia/dev-configs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This package provides:
|
|
12
|
+
- **ESLint rules** - Pre-configured for Node.js/TypeScript projects
|
|
13
|
+
- **Prettier settings** - Consistent formatting across all Ignis projects
|
|
14
|
+
- **TypeScript configs** - Shared base and common configurations
|
|
15
|
+
|
|
16
|
+
## Prettier Configuration
|
|
17
|
+
|
|
18
|
+
Automatic code formatting eliminates style debates.
|
|
19
|
+
|
|
20
|
+
**`.prettierrc.mjs`:**
|
|
21
|
+
```javascript
|
|
22
|
+
import { prettierConfigs } from '@venizia/dev-configs';
|
|
23
|
+
|
|
24
|
+
export default prettierConfigs;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Default Settings:**
|
|
28
|
+
|
|
29
|
+
| Setting | Value | Description |
|
|
30
|
+
|---------|-------|-------------|
|
|
31
|
+
| `bracketSpacing` | `true` | `{ foo: bar }` |
|
|
32
|
+
| `singleQuote` | `false` | `"string"` (double quotes) |
|
|
33
|
+
| `printWidth` | `100` | Maximum line length |
|
|
34
|
+
| `trailingComma` | `'all'` | `[1, 2, 3,]` |
|
|
35
|
+
| `arrowParens` | `'avoid'` | `x => x` not `(x) => x` |
|
|
36
|
+
| `semi` | `true` | Semicolons required |
|
|
37
|
+
|
|
38
|
+
**Customization:**
|
|
39
|
+
```javascript
|
|
40
|
+
import { prettierConfigs } from '@venizia/dev-configs';
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
...prettierConfigs,
|
|
44
|
+
printWidth: 120, // Override specific settings
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Usage:**
|
|
49
|
+
```bash
|
|
50
|
+
bun run prettier:cli # Check formatting
|
|
51
|
+
bun run prettier:fix # Auto-fix
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## ESLint Configuration
|
|
55
|
+
|
|
56
|
+
Prevents common errors and enforces best practices.
|
|
57
|
+
|
|
58
|
+
**`eslint.config.mjs`:**
|
|
59
|
+
```javascript
|
|
60
|
+
import { eslintConfigs } from '@venizia/dev-configs';
|
|
61
|
+
|
|
62
|
+
export default eslintConfigs;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Includes:**
|
|
66
|
+
- Pre-configured rules for Node.js/TypeScript (via `@minimaltech/eslint-node`)
|
|
67
|
+
- Disables `@typescript-eslint/no-explicit-any` by default
|
|
68
|
+
|
|
69
|
+
**Customization:**
|
|
70
|
+
```javascript
|
|
71
|
+
import { eslintConfigs } from '@venizia/dev-configs';
|
|
72
|
+
|
|
73
|
+
export default [
|
|
74
|
+
...eslintConfigs,
|
|
75
|
+
{
|
|
76
|
+
rules: {
|
|
77
|
+
'no-console': 'warn', // Add project-specific rules
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Usage:**
|
|
84
|
+
```bash
|
|
85
|
+
bun run eslint # Check for issues
|
|
86
|
+
bun run eslint --fix # Auto-fix issues
|
|
87
|
+
bun run lint:fix # Run both ESLint + Prettier
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## TypeScript Configuration
|
|
91
|
+
|
|
92
|
+
Use the centralized TypeScript configs:
|
|
93
|
+
|
|
94
|
+
**`tsconfig.json`:**
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"$schema": "http://json.schemastore.org/tsconfig",
|
|
98
|
+
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
99
|
+
"compilerOptions": {
|
|
100
|
+
"outDir": "dist",
|
|
101
|
+
"rootDir": "src",
|
|
102
|
+
"baseUrl": "src",
|
|
103
|
+
"paths": {
|
|
104
|
+
"@/*": ["./*"]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"include": ["src"],
|
|
108
|
+
"exclude": ["node_modules", "dist"]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**What's Included:**
|
|
113
|
+
|
|
114
|
+
| Option | Value | Purpose |
|
|
115
|
+
|--------|-------|---------|
|
|
116
|
+
| `target` | `ES2022` | Modern JavaScript features |
|
|
117
|
+
| `experimentalDecorators` | `true` | Required for Ignis decorators |
|
|
118
|
+
| `emitDecoratorMetadata` | `true` | Metadata reflection for DI |
|
|
119
|
+
| `strict` | `true` | Strict type checking |
|
|
120
|
+
| `skipLibCheck` | `true` | Faster compilation |
|
|
121
|
+
|
|
122
|
+
See [`@venizia/dev-configs` documentation](../../references/src-details/dev-configs) for full details.
|
|
123
|
+
|
|
124
|
+
## IDE Integration
|
|
125
|
+
|
|
126
|
+
### VS Code
|
|
127
|
+
|
|
128
|
+
**Recommended Extensions:**
|
|
129
|
+
- ESLint (`dbaeumer.vscode-eslint`)
|
|
130
|
+
- Prettier (`esbenp.prettier-vscode`)
|
|
131
|
+
|
|
132
|
+
**`.vscode/settings.json`:**
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"editor.formatOnSave": true,
|
|
136
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
137
|
+
"editor.codeActionsOnSave": {
|
|
138
|
+
"source.fixAll.eslint": "explicit"
|
|
139
|
+
},
|
|
140
|
+
"typescript.preferences.importModuleSpecifier": "relative"
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### WebStorm / IntelliJ
|
|
145
|
+
|
|
146
|
+
1. Go to **Settings → Languages & Frameworks → JavaScript → Prettier**
|
|
147
|
+
2. Enable "Run on save"
|
|
148
|
+
3. Go to **Settings → Languages & Frameworks → JavaScript → Code Quality Tools → ESLint**
|
|
149
|
+
4. Select "Automatic ESLint configuration"
|
|
150
|
+
|
|
151
|
+
## See Also
|
|
152
|
+
|
|
153
|
+
- [Naming Conventions](./naming-conventions) - File and class naming
|
|
154
|
+
- [Type Safety](./type-safety) - TypeScript best practices
|
|
155
|
+
- [@venizia/dev-configs Reference](../../references/src-details/dev-configs) - Full documentation
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Type Safety
|
|
2
|
+
|
|
3
|
+
Strict type safety ensures long-term maintainability and catches errors at compile-time.
|
|
4
|
+
|
|
5
|
+
## Avoid `any` and `unknown`
|
|
6
|
+
|
|
7
|
+
**Never use `any` or `unknown` as much as possible.** You must specify clear, descriptive types for all variables, parameters, and return values.
|
|
8
|
+
|
|
9
|
+
| Type | Problem | Solution |
|
|
10
|
+
|------|---------|----------|
|
|
11
|
+
| `any` | Bypasses all type checking | Use specific types or generics |
|
|
12
|
+
| `unknown` | Forces manual type checking | Use interfaces or type guards |
|
|
13
|
+
|
|
14
|
+
**Why?**
|
|
15
|
+
- **Maintenance**: Developers reading your code will know exactly what the data structure is
|
|
16
|
+
- **Refactoring**: Changing an interface automatically highlights all broken code
|
|
17
|
+
- **Documentation**: Types act as a self-documenting contract
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// ❌ BAD
|
|
21
|
+
const data: any = await fetchData();
|
|
22
|
+
const result: unknown = processData();
|
|
23
|
+
|
|
24
|
+
// ✅ GOOD
|
|
25
|
+
const data: TUserResponse = await fetchData();
|
|
26
|
+
const result: TProcessResult = processData();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Explicit Return Types
|
|
30
|
+
|
|
31
|
+
Always define explicit return types for **public methods** and **API handlers**.
|
|
32
|
+
|
|
33
|
+
**Why?**
|
|
34
|
+
- **Compiler Performance:** Speeds up TypeScript type checking in large projects
|
|
35
|
+
- **Safety:** Prevents accidental exposure of internal types or sensitive data
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// ✅ GOOD
|
|
39
|
+
public async findUser(id: string): Promise<User | null> {
|
|
40
|
+
// ...
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ❌ BAD (Implicit inference)
|
|
44
|
+
public async findUser(id: string) {
|
|
45
|
+
// Return type is inferred - can change unexpectedly
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Type Inference Patterns
|
|
50
|
+
|
|
51
|
+
### Zod Schema to Type
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Define schema
|
|
55
|
+
export const SignInRequestSchema = z.object({
|
|
56
|
+
email: z.string().email(),
|
|
57
|
+
password: z.string().min(8),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Infer type from schema
|
|
61
|
+
export type TSignInRequest = z.infer<typeof SignInRequestSchema>;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Const Assertion for Literal Types
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const RouteConfigs = {
|
|
68
|
+
GET_USERS: { method: 'GET', path: '/users' },
|
|
69
|
+
GET_USER_BY_ID: { method: 'GET', path: '/users/:id' },
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
// Type is now narrowed to literal values
|
|
73
|
+
type RouteKey = keyof typeof RouteConfigs; // 'GET_USERS' | 'GET_USER_BY_ID'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Generic Type Constraints
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
export class DefaultCRUDRepository<
|
|
80
|
+
Schema extends TTableSchemaWithId = TTableSchemaWithId
|
|
81
|
+
> {
|
|
82
|
+
// Schema is constrained to have an 'id' column
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface IAuthService<
|
|
86
|
+
SIRQ extends TSignInRequest = TSignInRequest,
|
|
87
|
+
SIRS = AnyObject,
|
|
88
|
+
> {
|
|
89
|
+
signIn(context: Context, opts: SIRQ): Promise<SIRS>;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Method Overloading for Conditional Returns
|
|
94
|
+
|
|
95
|
+
Use TypeScript method overloads when return types depend on input options:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
class Repository<T, R> {
|
|
99
|
+
// Overload 1: shouldReturn: false → data is null
|
|
100
|
+
create(opts: { data: T; options: { shouldReturn: false } }): Promise<{ count: number; data: null }>;
|
|
101
|
+
// Overload 2: shouldReturn: true (default) → data is R
|
|
102
|
+
create(opts: { data: T; options?: { shouldReturn?: true } }): Promise<{ count: number; data: R }>;
|
|
103
|
+
// Implementation signature
|
|
104
|
+
create(opts: { data: T; options?: { shouldReturn?: boolean } }): Promise<{ count: number; data: R | null }> {
|
|
105
|
+
// implementation
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Usage
|
|
110
|
+
const result1 = await repo.create({ data: user, options: { shouldReturn: false } });
|
|
111
|
+
// result1.data is typed as null
|
|
112
|
+
|
|
113
|
+
const result2 = await repo.create({ data: user });
|
|
114
|
+
// result2.data is typed as R (the entity type)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**When to use:**
|
|
118
|
+
- Return type varies based on boolean flag
|
|
119
|
+
- API with optional "return data" behavior
|
|
120
|
+
- Methods with conditional processing
|
|
121
|
+
|
|
122
|
+
## Type Guard Patterns
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Type guard function
|
|
126
|
+
function isUser(obj: unknown): obj is TUser {
|
|
127
|
+
return (
|
|
128
|
+
typeof obj === 'object' &&
|
|
129
|
+
obj !== null &&
|
|
130
|
+
'id' in obj &&
|
|
131
|
+
'email' in obj
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Usage
|
|
136
|
+
const data = await fetchData();
|
|
137
|
+
if (isUser(data)) {
|
|
138
|
+
// data is now typed as TUser
|
|
139
|
+
console.log(data.email);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Discriminated Unions
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
type TResult<T> =
|
|
147
|
+
| { success: true; data: T }
|
|
148
|
+
| { success: false; error: string };
|
|
149
|
+
|
|
150
|
+
function processResult<T>(result: TResult<T>) {
|
|
151
|
+
if (result.success) {
|
|
152
|
+
// TypeScript knows result.data exists
|
|
153
|
+
return result.data;
|
|
154
|
+
} else {
|
|
155
|
+
// TypeScript knows result.error exists
|
|
156
|
+
throw new Error(result.error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## See Also
|
|
162
|
+
|
|
163
|
+
- [Naming Conventions](./naming-conventions) - Type naming prefixes
|
|
164
|
+
- [Function Patterns](./function-patterns) - Typed function signatures
|
|
165
|
+
- [Advanced Patterns](./advanced-patterns) - Generic patterns
|