@mind-fold/open-flow 0.1.17 → 0.2.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/dist/configurators/templates.d.ts.map +1 -1
- package/dist/configurators/templates.js +17 -6
- package/dist/configurators/templates.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +82 -6
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/templates/commands/break-loop.txt +107 -0
- package/dist/templates/commands/check-cross-layer.txt +153 -0
- package/dist/templates/commands/finish-work.txt +129 -0
- package/dist/templates/commands/index.d.ts +9 -5
- package/dist/templates/commands/index.d.ts.map +1 -1
- package/dist/templates/commands/index.js +16 -5
- package/dist/templates/commands/index.js.map +1 -1
- package/dist/templates/commands/init-agent.txt +100 -9
- package/dist/templates/commands/sync-from-runtime.txt +140 -0
- package/dist/templates/markdown/flow.md.txt +96 -84
- package/dist/templates/markdown/index.d.ts +21 -4
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +27 -4
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/markdown/structure/backend/database-guidelines.md.txt +247 -0
- package/dist/templates/markdown/structure/backend/directory-structure.md.txt +153 -0
- package/dist/templates/markdown/structure/backend/error-handling.md.txt +257 -0
- package/dist/templates/markdown/structure/backend/index.md.txt +88 -0
- package/dist/templates/markdown/structure/backend/logging-guidelines.md.txt +212 -0
- package/dist/templates/markdown/structure/backend/quality-guidelines.md.txt +219 -0
- package/dist/templates/markdown/structure/backend/type-safety.md.txt +192 -0
- package/dist/templates/markdown/structure/flows/code-reuse-thinking-guide.md.txt +343 -0
- package/dist/templates/markdown/structure/flows/cross-layer-thinking-guide.md.txt +283 -0
- package/dist/templates/markdown/structure/flows/index.md.txt +133 -0
- package/dist/templates/markdown/structure/flows/pre-implementation-checklist.md.txt +182 -0
- package/dist/templates/markdown/structure/flows/spec-flow-template.md.txt +145 -0
- package/dist/templates/markdown/structure/frontend/component-guidelines.md.txt +335 -0
- package/dist/templates/markdown/structure/frontend/directory-structure.md.txt +172 -0
- package/dist/templates/markdown/structure/frontend/hook-guidelines.md.txt +287 -0
- package/dist/templates/markdown/structure/frontend/index.md.txt +91 -0
- package/dist/templates/markdown/structure/frontend/quality-guidelines.md.txt +274 -0
- package/dist/templates/markdown/structure/frontend/state-management.md.txt +293 -0
- package/dist/templates/markdown/structure/frontend/type-safety.md.txt +275 -0
- package/package.json +2 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Backend Development Guidelines
|
|
2
|
+
|
|
3
|
+
> Best practices for backend development
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This collection of guidelines covers essential patterns and best practices for building robust backend applications.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Guidelines Index
|
|
14
|
+
|
|
15
|
+
### Core Patterns
|
|
16
|
+
|
|
17
|
+
| Guide | Description |
|
|
18
|
+
|-------|-------------|
|
|
19
|
+
| [Directory Structure](./directory-structure.md) | Module organization and file layout |
|
|
20
|
+
| [Type Safety](./type-safety.md) | Zod validation, non-null handling, type inference |
|
|
21
|
+
| [Quality Guidelines](./quality-guidelines.md) | Forbidden patterns, code standards |
|
|
22
|
+
|
|
23
|
+
### Database & Data
|
|
24
|
+
|
|
25
|
+
| Guide | Description |
|
|
26
|
+
|-------|-------------|
|
|
27
|
+
| [Database Guidelines](./database-guidelines.md) | ORM patterns, batch operations, migrations |
|
|
28
|
+
|
|
29
|
+
### Operations
|
|
30
|
+
|
|
31
|
+
| Guide | Description |
|
|
32
|
+
|-------|-------------|
|
|
33
|
+
| [Logging Guidelines](./logging-guidelines.md) | Structured logging, log levels, context |
|
|
34
|
+
| [Error Handling](./error-handling.md) | Error types, handling strategies |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Reference
|
|
39
|
+
|
|
40
|
+
### Directory Structure
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
server/
|
|
44
|
+
├── db/ # Database (client, schema)
|
|
45
|
+
├── lib/ # Shared utilities
|
|
46
|
+
├── middleware/ # Middleware
|
|
47
|
+
├── routes/ # API routes (domain-driven)
|
|
48
|
+
│ └── {domain}/
|
|
49
|
+
│ ├── types.ts # Zod schemas + types
|
|
50
|
+
│ ├── router.ts # Route definitions
|
|
51
|
+
│ ├── procedures/ # One file per endpoint
|
|
52
|
+
│ └── lib/ # Domain-specific logic
|
|
53
|
+
└── types.ts # Shared types
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Key Rules
|
|
57
|
+
|
|
58
|
+
| Rule | Description |
|
|
59
|
+
|------|-------------|
|
|
60
|
+
| No `!` assertions | Use null checks with local variables |
|
|
61
|
+
| No `console.log` | Use structured logger |
|
|
62
|
+
| No `await` in loops | Use batch queries with `inArray` |
|
|
63
|
+
| Validate all inputs | Use Zod schemas |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Getting Started
|
|
68
|
+
|
|
69
|
+
1. **New to this codebase?** Start with [Directory Structure](./directory-structure.md)
|
|
70
|
+
2. **Setting up database?** Read [Database Guidelines](./database-guidelines.md)
|
|
71
|
+
3. **Before commit?** Check [Quality Guidelines](./quality-guidelines.md)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Common Tasks
|
|
76
|
+
|
|
77
|
+
### Create a New API Module
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
mkdir -p server/routes/{domain}/procedures
|
|
81
|
+
touch server/routes/{domain}/{types.ts,router.ts}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
See: [Directory Structure](./directory-structure.md)
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
**Language**: All documentation must be written in **English**.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Logging Guidelines
|
|
2
|
+
|
|
3
|
+
> Structured logging best practices
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Rules
|
|
8
|
+
|
|
9
|
+
1. **Never use `console.log`** - Use structured logger
|
|
10
|
+
2. **Include context** - Add relevant IDs and metadata
|
|
11
|
+
3. **Use appropriate levels** - Match severity to log level
|
|
12
|
+
4. **Use snake_case** - For log message names
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Log Levels
|
|
17
|
+
|
|
18
|
+
| Level | Use Case | Example |
|
|
19
|
+
|-------|----------|---------|
|
|
20
|
+
| `error` | Errors that need attention | Database connection failed |
|
|
21
|
+
| `warn` | Potential issues, degraded state | Rate limit approaching |
|
|
22
|
+
| `info` | Normal operations, business events | User created, order placed |
|
|
23
|
+
| `debug` | Development details | Query executed, cache hit |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Structured Logging
|
|
28
|
+
|
|
29
|
+
### Basic Pattern
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// ✅ GOOD: Structured with context
|
|
33
|
+
logger.info('user_created', {
|
|
34
|
+
userId: user.id,
|
|
35
|
+
email: user.email
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
logger.error('payment_failed', error, {
|
|
39
|
+
userId: user.id,
|
|
40
|
+
amount: order.total
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ❌ BAD: Unstructured string concatenation
|
|
44
|
+
console.log('User created: ' + user.id);
|
|
45
|
+
logger.info('Error: ' + error.message);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### With Request Context
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// In route handler
|
|
52
|
+
app.get('/api/users/:id', async (c) => {
|
|
53
|
+
const logger = c.get('logger');
|
|
54
|
+
const userId = c.req.param('id');
|
|
55
|
+
|
|
56
|
+
logger.info('fetching_user', { userId });
|
|
57
|
+
|
|
58
|
+
const user = await getUser(userId);
|
|
59
|
+
if (!user) {
|
|
60
|
+
logger.warn('user_not_found', { userId });
|
|
61
|
+
return c.json({ error: 'Not found' }, 404);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
logger.info('user_fetched', { userId, email: user.email });
|
|
65
|
+
return c.json(user);
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Standard Log Messages
|
|
72
|
+
|
|
73
|
+
Use consistent message names across the codebase:
|
|
74
|
+
|
|
75
|
+
| Message | Level | When |
|
|
76
|
+
|---------|-------|------|
|
|
77
|
+
| `request_start` | info | Request begins |
|
|
78
|
+
| `request_end` | info | Request completes |
|
|
79
|
+
| `request_error` | error | Unhandled error |
|
|
80
|
+
| `{entity}_created` | info | Entity created |
|
|
81
|
+
| `{entity}_updated` | info | Entity updated |
|
|
82
|
+
| `{entity}_deleted` | info | Entity deleted |
|
|
83
|
+
| `{entity}_not_found` | warn | Entity lookup failed |
|
|
84
|
+
| `validation_error` | warn | Input validation failed |
|
|
85
|
+
| `auth_failed` | warn | Authentication failed |
|
|
86
|
+
| `permission_denied` | warn | Authorization failed |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Error Logging
|
|
91
|
+
|
|
92
|
+
Always include the error object for stack traces:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
try {
|
|
96
|
+
await riskyOperation();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
logger.error('operation_failed', error, {
|
|
100
|
+
operationId: opId,
|
|
101
|
+
step: currentStep,
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
logger.error('operation_failed', undefined, {
|
|
105
|
+
error: String(error)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
throw error; // Re-throw or handle appropriately
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Performance Logging
|
|
115
|
+
|
|
116
|
+
Track duration for slow operations:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
async function syncData(logger: Logger) {
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await performSync();
|
|
124
|
+
|
|
125
|
+
const duration = Date.now() - startTime;
|
|
126
|
+
logger.info('sync_completed', { durationMs: duration });
|
|
127
|
+
|
|
128
|
+
if (duration > 5000) {
|
|
129
|
+
logger.warn('sync_slow', { durationMs: duration });
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.error('sync_failed', error, {
|
|
133
|
+
durationMs: Date.now() - startTime
|
|
134
|
+
});
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## What NOT to Log
|
|
143
|
+
|
|
144
|
+
| Never Log | Reason |
|
|
145
|
+
|-----------|--------|
|
|
146
|
+
| Passwords | Security |
|
|
147
|
+
| API keys / tokens | Security |
|
|
148
|
+
| Full credit card numbers | PCI compliance |
|
|
149
|
+
| Personal health info | HIPAA compliance |
|
|
150
|
+
| Session cookies | Security |
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// ❌ BAD
|
|
154
|
+
logger.info('login_attempt', { email, password });
|
|
155
|
+
|
|
156
|
+
// ✅ GOOD
|
|
157
|
+
logger.info('login_attempt', { email });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Logger Setup Example
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// lib/logger.ts
|
|
166
|
+
export interface Logger {
|
|
167
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
168
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
169
|
+
error(message: string, error?: Error, meta?: Record<string, unknown>): void;
|
|
170
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function createLogger(context: Record<string, unknown> = {}): Logger {
|
|
174
|
+
const log = (level: string, message: string, meta: Record<string, unknown> = {}) => {
|
|
175
|
+
const entry = {
|
|
176
|
+
timestamp: new Date().toISOString(),
|
|
177
|
+
level,
|
|
178
|
+
message,
|
|
179
|
+
...context,
|
|
180
|
+
...meta,
|
|
181
|
+
};
|
|
182
|
+
console.log(JSON.stringify(entry));
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
info: (message, meta) => log('info', message, meta),
|
|
187
|
+
warn: (message, meta) => log('warn', message, meta),
|
|
188
|
+
error: (message, error, meta) => log('error', message, {
|
|
189
|
+
...meta,
|
|
190
|
+
error: error?.message,
|
|
191
|
+
stack: error?.stack,
|
|
192
|
+
}),
|
|
193
|
+
debug: (message, meta) => log('debug', message, meta),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Summary
|
|
201
|
+
|
|
202
|
+
| Rule | Description |
|
|
203
|
+
|------|-------------|
|
|
204
|
+
| No `console.log` | Use structured logger |
|
|
205
|
+
| snake_case messages | `user_created` not `User Created` |
|
|
206
|
+
| Include IDs | Add entity IDs to context |
|
|
207
|
+
| No sensitive data | Never log passwords/tokens |
|
|
208
|
+
| Track duration | Log `durationMs` for slow ops |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
**Language**: All documentation must be written in **English**.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Quality Guidelines
|
|
2
|
+
|
|
3
|
+
> Code standards and forbidden patterns
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Before Every Commit
|
|
8
|
+
|
|
9
|
+
Run these checks before committing:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Must pass with 0 errors
|
|
13
|
+
pnpm lint
|
|
14
|
+
|
|
15
|
+
# Must pass with no type errors
|
|
16
|
+
pnpm type-check
|
|
17
|
+
|
|
18
|
+
# Run tests
|
|
19
|
+
pnpm test
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Forbidden Patterns
|
|
25
|
+
|
|
26
|
+
### Non-Null Assertions
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// ❌ FORBIDDEN
|
|
30
|
+
const name = user!.name;
|
|
31
|
+
const data = response!.data;
|
|
32
|
+
|
|
33
|
+
// ✅ REQUIRED
|
|
34
|
+
const user = await getUser(id);
|
|
35
|
+
if (!user) {
|
|
36
|
+
throw new NotFoundError('User not found');
|
|
37
|
+
}
|
|
38
|
+
const name = user.name;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Console.log
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// ❌ FORBIDDEN
|
|
45
|
+
console.log('Debug:', data);
|
|
46
|
+
console.error('Error:', error);
|
|
47
|
+
|
|
48
|
+
// ✅ REQUIRED
|
|
49
|
+
logger.debug('processing_data', { data });
|
|
50
|
+
logger.error('operation_failed', error);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Await in Loops
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// ❌ FORBIDDEN
|
|
57
|
+
for (const id of ids) {
|
|
58
|
+
await db.select().from(users).where(eq(users.id, id));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ✅ REQUIRED
|
|
62
|
+
const results = await db
|
|
63
|
+
.select()
|
|
64
|
+
.from(users)
|
|
65
|
+
.where(inArray(users.id, ids));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Type Assertions Without Validation
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// ❌ FORBIDDEN
|
|
72
|
+
const data = input as UserData;
|
|
73
|
+
const config = JSON.parse(str) as Config;
|
|
74
|
+
|
|
75
|
+
// ✅ REQUIRED
|
|
76
|
+
const data = userDataSchema.parse(input);
|
|
77
|
+
const config = configSchema.parse(JSON.parse(str));
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Any Type
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// ❌ FORBIDDEN
|
|
84
|
+
function process(data: any) { ... }
|
|
85
|
+
const result: any = await fetch(...);
|
|
86
|
+
|
|
87
|
+
// ✅ REQUIRED
|
|
88
|
+
function process(data: unknown) {
|
|
89
|
+
const validated = schema.parse(data);
|
|
90
|
+
...
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Required Patterns
|
|
97
|
+
|
|
98
|
+
### Error Handling
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Always handle errors explicitly
|
|
102
|
+
try {
|
|
103
|
+
await riskyOperation();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error('operation_failed', error);
|
|
106
|
+
throw new AppError('Operation failed', { cause: error });
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Input Validation
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// All API inputs must be validated
|
|
114
|
+
app.post('/users', async (c) => {
|
|
115
|
+
const input = createUserSchema.parse(await c.req.json());
|
|
116
|
+
// Now input is type-safe
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Null Checks
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Always check nullable values before use
|
|
124
|
+
const user = await getUser(id);
|
|
125
|
+
if (!user) {
|
|
126
|
+
return c.json({ error: 'User not found' }, 404);
|
|
127
|
+
}
|
|
128
|
+
// TypeScript knows user is not null here
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Code Style
|
|
134
|
+
|
|
135
|
+
### Naming
|
|
136
|
+
|
|
137
|
+
| Type | Convention | Example |
|
|
138
|
+
|------|------------|---------|
|
|
139
|
+
| Variables | camelCase | `userName`, `isActive` |
|
|
140
|
+
| Functions | camelCase | `createUser`, `validateInput` |
|
|
141
|
+
| Types/Interfaces | PascalCase | `UserData`, `CreateUserInput` |
|
|
142
|
+
| Constants | SCREAMING_SNAKE | `MAX_RETRIES`, `API_TIMEOUT` |
|
|
143
|
+
| Files | kebab-case | `user-service.ts`, `auth-middleware.ts` |
|
|
144
|
+
|
|
145
|
+
### Function Length
|
|
146
|
+
|
|
147
|
+
- Keep functions under 50 lines
|
|
148
|
+
- Extract helper functions for complex logic
|
|
149
|
+
- One function = one responsibility
|
|
150
|
+
|
|
151
|
+
### Comments
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// ✅ GOOD: Explain WHY, not WHAT
|
|
155
|
+
// Skip validation for internal calls to avoid performance overhead
|
|
156
|
+
const data = trustInternalData(input);
|
|
157
|
+
|
|
158
|
+
// ❌ BAD: Obvious comment
|
|
159
|
+
// Get user by id
|
|
160
|
+
const user = await getUser(id);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Documentation
|
|
166
|
+
|
|
167
|
+
### API Endpoints
|
|
168
|
+
|
|
169
|
+
Document every public API endpoint:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
/**
|
|
173
|
+
* Create a new user
|
|
174
|
+
*
|
|
175
|
+
* @route POST /api/users
|
|
176
|
+
* @param email - User's email address
|
|
177
|
+
* @param name - User's display name
|
|
178
|
+
* @returns Created user object
|
|
179
|
+
* @throws 400 - Invalid input
|
|
180
|
+
* @throws 409 - Email already exists
|
|
181
|
+
*/
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Complex Logic
|
|
185
|
+
|
|
186
|
+
Document non-obvious business logic:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
/**
|
|
190
|
+
* Calculate user's subscription tier based on usage.
|
|
191
|
+
*
|
|
192
|
+
* Tier calculation:
|
|
193
|
+
* - Free: < 100 requests/month
|
|
194
|
+
* - Pro: 100-10000 requests/month
|
|
195
|
+
* - Enterprise: > 10000 requests/month
|
|
196
|
+
*
|
|
197
|
+
* Note: Calculated at month end, not real-time.
|
|
198
|
+
*/
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Checklist
|
|
204
|
+
|
|
205
|
+
Before submitting code for review:
|
|
206
|
+
|
|
207
|
+
- [ ] No `console.log` statements
|
|
208
|
+
- [ ] No `!` non-null assertions
|
|
209
|
+
- [ ] No `any` types
|
|
210
|
+
- [ ] No `await` in loops
|
|
211
|
+
- [ ] All inputs validated with Zod
|
|
212
|
+
- [ ] Errors logged with context
|
|
213
|
+
- [ ] `pnpm lint` passes
|
|
214
|
+
- [ ] `pnpm type-check` passes
|
|
215
|
+
- [ ] Tests pass
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
**Language**: All documentation must be written in **English**.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Type Safety
|
|
2
|
+
|
|
3
|
+
> Zod validation, non-null handling, and type inference patterns
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Principles
|
|
8
|
+
|
|
9
|
+
1. **Validate at boundaries** - All external inputs must be validated
|
|
10
|
+
2. **Infer from schemas** - Don't duplicate type definitions
|
|
11
|
+
3. **No assertions** - Never use `!` or `as` without validation
|
|
12
|
+
4. **Explicit nullability** - Handle null/undefined explicitly
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Zod Schema Patterns
|
|
17
|
+
|
|
18
|
+
### Basic Schema
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
|
|
23
|
+
export const userSchema = z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
email: z.string().email(),
|
|
26
|
+
name: z.string().min(1).max(100),
|
|
27
|
+
age: z.number().int().positive().optional(),
|
|
28
|
+
role: z.enum(['admin', 'user', 'guest']),
|
|
29
|
+
createdAt: z.date(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Infer type from schema
|
|
33
|
+
export type User = z.infer<typeof userSchema>;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Input vs Output Schemas
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Input: what client sends
|
|
40
|
+
export const createUserInput = z.object({
|
|
41
|
+
email: z.string().email(),
|
|
42
|
+
name: z.string().min(1),
|
|
43
|
+
password: z.string().min(8),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Output: what server returns (no password!)
|
|
47
|
+
export const userOutput = z.object({
|
|
48
|
+
id: z.string(),
|
|
49
|
+
email: z.string(),
|
|
50
|
+
name: z.string(),
|
|
51
|
+
createdAt: z.date(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type CreateUserInput = z.infer<typeof createUserInput>;
|
|
55
|
+
export type UserOutput = z.infer<typeof userOutput>;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Partial and Pick
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// For updates - all fields optional
|
|
62
|
+
export const updateUserInput = createUserInput.partial();
|
|
63
|
+
|
|
64
|
+
// Pick specific fields
|
|
65
|
+
export const userSummary = userSchema.pick({
|
|
66
|
+
id: true,
|
|
67
|
+
name: true,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## No Non-Null Assertions
|
|
74
|
+
|
|
75
|
+
**Never use `!` operator.** It bypasses TypeScript's null safety.
|
|
76
|
+
|
|
77
|
+
### Problem
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// ❌ BAD: Crashes if user is null
|
|
81
|
+
const user = await getUser(id);
|
|
82
|
+
const name = user!.name; // TypeScript trusts you, runtime doesn't
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Solution: Guard with Local Variable
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// ✅ GOOD: Explicit null check
|
|
89
|
+
const user = await getUser(id);
|
|
90
|
+
if (!user) {
|
|
91
|
+
throw new NotFoundError('User not found');
|
|
92
|
+
}
|
|
93
|
+
// TypeScript now knows user is not null
|
|
94
|
+
const name = user.name; // Safe access
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Solution: Early Return
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// ✅ GOOD: Early return pattern
|
|
101
|
+
async function getUserName(id: string): Promise<string> {
|
|
102
|
+
const user = await getUser(id);
|
|
103
|
+
if (!user) {
|
|
104
|
+
throw new NotFoundError('User not found');
|
|
105
|
+
}
|
|
106
|
+
return user.name;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Type Inference from Database
|
|
113
|
+
|
|
114
|
+
### Drizzle ORM
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
118
|
+
|
|
119
|
+
export const users = sqliteTable('users', {
|
|
120
|
+
id: text('id').primaryKey(),
|
|
121
|
+
email: text('email').notNull(),
|
|
122
|
+
name: text('name').notNull(),
|
|
123
|
+
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Infer types from schema
|
|
127
|
+
export type User = typeof users.$inferSelect;
|
|
128
|
+
export type InsertUser = typeof users.$inferInsert;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Align Zod with Database
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Database schema is source of truth
|
|
135
|
+
// Zod schema for API validation should match
|
|
136
|
+
|
|
137
|
+
export const createUserInput = z.object({
|
|
138
|
+
email: z.string().email(),
|
|
139
|
+
name: z.string().min(1),
|
|
140
|
+
}) satisfies z.ZodType<Omit<InsertUser, 'id' | 'createdAt'>>;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Forbidden Patterns
|
|
146
|
+
|
|
147
|
+
| Pattern | Problem | Solution |
|
|
148
|
+
|---------|---------|----------|
|
|
149
|
+
| `user!.name` | Runtime crash if null | Null check + local variable |
|
|
150
|
+
| `data as User` | No runtime validation | Use Zod `.parse()` |
|
|
151
|
+
| `any` type | Disables type checking | Use `unknown` + validation |
|
|
152
|
+
| Duplicate types | Drift between definitions | Infer from single source |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Best Practices
|
|
157
|
+
|
|
158
|
+
### DO
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Parse external data
|
|
162
|
+
const data = userSchema.parse(externalInput);
|
|
163
|
+
|
|
164
|
+
// Use safeParse for graceful handling
|
|
165
|
+
const result = userSchema.safeParse(input);
|
|
166
|
+
if (!result.success) {
|
|
167
|
+
return { error: result.error.format() };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Explicit null handling
|
|
171
|
+
const user = await db.query.users.findFirst({ where: eq(users.id, id) });
|
|
172
|
+
if (!user) {
|
|
173
|
+
throw new NotFoundError(`User ${id} not found`);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### DON'T
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Trust external data
|
|
181
|
+
const data = externalInput as User; // ❌
|
|
182
|
+
|
|
183
|
+
// Use non-null assertion
|
|
184
|
+
const name = user!.name; // ❌
|
|
185
|
+
|
|
186
|
+
// Ignore parse errors
|
|
187
|
+
const data = userSchema.parse(input); // ❌ Throws on invalid
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
**Language**: All documentation must be written in **English**.
|