@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.
Files changed (44) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/api-usage-examples.md +1 -0
  3. package/wiki/best-practices/code-style-standards/advanced-patterns.md +259 -0
  4. package/wiki/best-practices/code-style-standards/constants-configuration.md +225 -0
  5. package/wiki/best-practices/code-style-standards/control-flow.md +245 -0
  6. package/wiki/best-practices/code-style-standards/documentation.md +221 -0
  7. package/wiki/best-practices/code-style-standards/function-patterns.md +142 -0
  8. package/wiki/best-practices/code-style-standards/index.md +110 -0
  9. package/wiki/best-practices/code-style-standards/naming-conventions.md +174 -0
  10. package/wiki/best-practices/code-style-standards/route-definitions.md +150 -0
  11. package/wiki/best-practices/code-style-standards/tooling.md +155 -0
  12. package/wiki/best-practices/code-style-standards/type-safety.md +165 -0
  13. package/wiki/best-practices/common-pitfalls.md +164 -3
  14. package/wiki/best-practices/contribution-workflow.md +1 -1
  15. package/wiki/best-practices/data-modeling.md +102 -2
  16. package/wiki/best-practices/error-handling.md +468 -0
  17. package/wiki/best-practices/index.md +204 -21
  18. package/wiki/best-practices/performance-optimization.md +180 -0
  19. package/wiki/best-practices/security-guidelines.md +249 -0
  20. package/wiki/best-practices/testing-strategies.md +620 -0
  21. package/wiki/changelogs/2026-01-05-range-queries-content-range.md +184 -0
  22. package/wiki/changelogs/2026-01-06-basic-authentication.md +103 -0
  23. package/wiki/changelogs/2026-01-07-controller-route-customization.md +209 -0
  24. package/wiki/changelogs/index.md +3 -0
  25. package/wiki/guides/core-concepts/components-guide.md +1 -1
  26. package/wiki/guides/core-concepts/persistent/models.md +10 -0
  27. package/wiki/guides/tutorials/complete-installation.md +1 -1
  28. package/wiki/guides/tutorials/testing.md +1 -1
  29. package/wiki/references/base/bootstrapping.md +4 -3
  30. package/wiki/references/base/components.md +47 -29
  31. package/wiki/references/base/controllers.md +220 -24
  32. package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
  33. package/wiki/references/base/middlewares.md +37 -3
  34. package/wiki/references/base/models.md +40 -2
  35. package/wiki/references/base/providers.md +1 -2
  36. package/wiki/references/base/repositories/index.md +3 -1
  37. package/wiki/references/base/services.md +2 -2
  38. package/wiki/references/components/authentication.md +261 -247
  39. package/wiki/references/helpers/index.md +1 -1
  40. package/wiki/references/helpers/socket-io.md +1 -1
  41. package/wiki/references/quick-reference.md +2 -2
  42. package/wiki/references/src-details/core.md +1 -1
  43. package/wiki/references/utilities/statuses.md +4 -4
  44. 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