yinzerflow 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -26
- package/docs/configuration/configuration.md +815 -0
- package/docs/core/core-concepts.md +801 -0
- package/docs/core/error-handling.md +391 -153
- package/docs/core/logging.md +426 -68
- package/docs/modules/body-parsing.md +561 -0
- package/docs/modules/cors.md +369 -0
- package/docs/modules/index.md +125 -0
- package/docs/modules/ip-security.md +280 -0
- package/docs/modules/rate-limiting.md +795 -0
- package/index.d.ts +278 -76
- package/index.js +18 -18
- package/index.js.map +17 -8
- package/package.json +5 -3
- package/docs/configuration/advanced-configuration-options.md +0 -302
- package/docs/configuration/configuration-patterns.md +0 -500
- package/docs/core/context.md +0 -230
- package/docs/core/examples.md +0 -444
- package/docs/core/request.md +0 -161
- package/docs/core/response.md +0 -212
- package/docs/core/routes.md +0 -720
- package/docs/quick-reference.md +0 -346
- package/docs/security/body-parsing.md +0 -296
- package/docs/security/cors.md +0 -189
- package/docs/security/ip-security.md +0 -234
- package/docs/security/security-overview.md +0 -282
- package/docs/start-here.md +0 -184
package/docs/core/context.md
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
# Context Object
|
|
2
|
-
|
|
3
|
-
The Context object is the central interface for all YinzerFlow route handlers and hooks. It provides access to the request data, response controls, and maintains the complete request lifecycle state.
|
|
4
|
-
|
|
5
|
-
## Configuration
|
|
6
|
-
|
|
7
|
-
Context objects are automatically created and provided to all handlers - no configuration required:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { YinzerFlow } from 'yinzerflow';
|
|
11
|
-
|
|
12
|
-
const app = new YinzerFlow({ port: 3000 });
|
|
13
|
-
|
|
14
|
-
// Context is automatically provided to all handlers
|
|
15
|
-
app.get('/api/data', (ctx) => {
|
|
16
|
-
// Access request data
|
|
17
|
-
const { path, method, headers } = ctx.request;
|
|
18
|
-
|
|
19
|
-
// Control response
|
|
20
|
-
ctx.response.setStatusCode(200);
|
|
21
|
-
|
|
22
|
-
return { message: 'Success' };
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Examples
|
|
27
|
-
|
|
28
|
-
### Basic Example
|
|
29
|
-
|
|
30
|
-
See [Basic Handler Pattern](./examples.md#basic-handler-pattern) for a complete example showing how to use the context object.
|
|
31
|
-
|
|
32
|
-
For detailed information about request properties and methods, see [Request Object Documentation](./request.md).
|
|
33
|
-
For detailed information about response methods and capabilities, see [Response Object Documentation](./response.md).
|
|
34
|
-
|
|
35
|
-
## Common Use Cases
|
|
36
|
-
|
|
37
|
-
- **Request Data Access**: Extract headers, body, query parameters, and route parameters for processing
|
|
38
|
-
- **Response Control**: Set status codes, add headers, and control response formatting
|
|
39
|
-
- **Authentication**: Access authorization headers and client IP for security validation
|
|
40
|
-
- **Content Negotiation**: Handle Accept headers and Content-Type for proper response formatting
|
|
41
|
-
- **Error Handling**: Provide context to error handlers for detailed error responses
|
|
42
|
-
- **Logging and Monitoring**: Access request metadata for logging and analytics
|
|
43
|
-
|
|
44
|
-
## Context Structure
|
|
45
|
-
|
|
46
|
-
The Context object contains two main properties:
|
|
47
|
-
|
|
48
|
-
### Request Object (`ctx.request`)
|
|
49
|
-
Contains all incoming request data including headers, body, query parameters, route parameters, and metadata. See [Request Object Documentation](./request.md) for detailed information about request properties and methods.
|
|
50
|
-
|
|
51
|
-
### Response Object (`ctx.response`)
|
|
52
|
-
Provides methods to control the HTTP response including status codes, headers, and response formatting. See [Response Object Documentation](./response.md) for detailed information about response methods and capabilities.
|
|
53
|
-
|
|
54
|
-
## Context State
|
|
55
|
-
|
|
56
|
-
The Context object provides a powerful state system that allows you to store and share custom data throughout the request lifecycle. This is perfect for authentication, middleware data, request-scoped variables, and custom context information.
|
|
57
|
-
|
|
58
|
-
### What is Context State?
|
|
59
|
-
|
|
60
|
-
Context state is a request-scoped object (`ctx.state`) that persists data throughout the entire request lifecycle. Unlike global variables, state is isolated to each individual request and automatically garbage collected when the request completes.
|
|
61
|
-
|
|
62
|
-
### Basic Usage
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
app.get('/api/users', async (ctx) => {
|
|
66
|
-
// Store custom data in state
|
|
67
|
-
ctx.state.user = { id: 1, name: 'John' };
|
|
68
|
-
ctx.state.requestId = generateRequestId();
|
|
69
|
-
ctx.state.timestamp = Date.now();
|
|
70
|
-
|
|
71
|
-
// Access the data later
|
|
72
|
-
console.log(ctx.state.user.name); // "John"
|
|
73
|
-
console.log(ctx.state.requestId); // "req-123"
|
|
74
|
-
console.log(ctx.state.timestamp); // 1703123456789
|
|
75
|
-
|
|
76
|
-
return { users: ['John', 'Jane'] };
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Advanced Usage with Type Safety
|
|
81
|
-
|
|
82
|
-
For full type safety, you can extend the context with custom state types:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
// Define your custom state interface
|
|
86
|
-
interface AuthContext extends InternalHandlerCallbackGenerics {
|
|
87
|
-
state: {
|
|
88
|
-
user: User;
|
|
89
|
-
permissions: string[];
|
|
90
|
-
requestId: string;
|
|
91
|
-
session: Session;
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Use it in your route handler
|
|
96
|
-
const authHandler: HandlerCallback<AuthContext> = async (ctx) => {
|
|
97
|
-
// Fully type-safe access to state
|
|
98
|
-
console.log(ctx.state.user.name); // Type: string
|
|
99
|
-
console.log(ctx.state.permissions[0]); // Type: string
|
|
100
|
-
console.log(ctx.state.session.expiresAt); // Type: Date
|
|
101
|
-
|
|
102
|
-
// No type assertions needed!
|
|
103
|
-
const user = ctx.state.user; // Type: User
|
|
104
|
-
const permissions = ctx.state.permissions; // Type: string[]
|
|
105
|
-
|
|
106
|
-
return { message: 'Authenticated', user };
|
|
107
|
-
};
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### State Lifecycle
|
|
111
|
-
|
|
112
|
-
State data follows the request lifecycle:
|
|
113
|
-
|
|
114
|
-
1. **Request Start**: State object is created as empty object
|
|
115
|
-
2. **Global Hooks**: `beforeAll` hooks can populate state
|
|
116
|
-
3. **Route Hooks**: `beforeHooks` can access and modify state
|
|
117
|
-
4. **Route Handler**: Your handler can access and modify state
|
|
118
|
-
5. **Route Hooks**: `afterHooks` can access state and modify response
|
|
119
|
-
6. **Global Hooks**: `afterAll` hooks can access state and modify response
|
|
120
|
-
7. **Request End**: State is automatically garbage collected
|
|
121
|
-
|
|
122
|
-
### State Inheritance in Route Groups
|
|
123
|
-
|
|
124
|
-
State can be shared across route groups and inherited by nested routes:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
app.group('/api/v1', (api) => {
|
|
128
|
-
// Group-level middleware sets state
|
|
129
|
-
api.beforeAll([async (ctx) => {
|
|
130
|
-
ctx.state.apiVersion = 'v1';
|
|
131
|
-
ctx.state.environment = process.env.NODE_ENV;
|
|
132
|
-
}]);
|
|
133
|
-
|
|
134
|
-
api.group('/admin', (admin) => {
|
|
135
|
-
// Admin-specific middleware
|
|
136
|
-
admin.beforeAll([async (ctx) => {
|
|
137
|
-
ctx.state.requiresAuth = true;
|
|
138
|
-
ctx.state.adminOnly = true;
|
|
139
|
-
}]);
|
|
140
|
-
|
|
141
|
-
// Routes inherit all state from parent groups
|
|
142
|
-
admin.get('/users', async (ctx) => {
|
|
143
|
-
console.log(ctx.state.apiVersion); // "v1"
|
|
144
|
-
console.log(ctx.state.environment); // "production"
|
|
145
|
-
console.log(ctx.state.requiresAuth); // true
|
|
146
|
-
console.log(ctx.state.adminOnly); // true
|
|
147
|
-
|
|
148
|
-
return { users: ['Admin1', 'Admin2'] };
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Common Use Cases
|
|
155
|
-
|
|
156
|
-
- **Authentication**: Store user information and permissions
|
|
157
|
-
- **Request Tracking**: Generate and store request IDs for logging
|
|
158
|
-
- **Middleware Data**: Pass data between middleware and route handlers
|
|
159
|
-
- **Session Management**: Store session information and user preferences
|
|
160
|
-
- **Rate Limiting**: Track request counts and limits per user/IP
|
|
161
|
-
- **Custom Headers**: Store computed headers for later use
|
|
162
|
-
- **Validation Results**: Cache validation results to avoid re-validation
|
|
163
|
-
- **Database Connections**: Store database connection pools or transactions
|
|
164
|
-
|
|
165
|
-
### Best Practices
|
|
166
|
-
|
|
167
|
-
- **Keep state minimal**: Only store data that's actually needed
|
|
168
|
-
- **Use descriptive keys**: `ctx.state.user` is better than `ctx.state.u`
|
|
169
|
-
- **Validate state access**: Check if properties exist before using them
|
|
170
|
-
- **Type your state**: Use TypeScript interfaces for complex state structures
|
|
171
|
-
- **Don't store sensitive data**: State is not encrypted or secured
|
|
172
|
-
- **Clean up resources**: Release any resources (like DB connections) when done
|
|
173
|
-
|
|
174
|
-
### Security Considerations
|
|
175
|
-
|
|
176
|
-
YinzerFlow implements several security measures for context state:
|
|
177
|
-
|
|
178
|
-
#### 🛡️ Request Isolation
|
|
179
|
-
- **Problem**: State data could leak between requests
|
|
180
|
-
- **YinzerFlow Solution**: Each request gets its own isolated state object
|
|
181
|
-
|
|
182
|
-
#### 🛡️ Type Safety
|
|
183
|
-
- **Problem**: Unchecked state access can lead to runtime errors
|
|
184
|
-
- **YinzerFlow Solution**: TypeScript generics provide compile-time type checking
|
|
185
|
-
|
|
186
|
-
#### 🛡️ Memory Management
|
|
187
|
-
- **Problem**: State data could accumulate and cause memory leaks
|
|
188
|
-
- **YinzerFlow Solution**: State is automatically garbage collected after each request
|
|
189
|
-
|
|
190
|
-
These security measures ensure YinzerFlow's context state implementation follows security best practices and prevents common attack vectors while maintaining spec compliance.
|
|
191
|
-
|
|
192
|
-
## TypeScript Support
|
|
193
|
-
|
|
194
|
-
YinzerFlow provides full TypeScript support for context objects with generic type parameters.
|
|
195
|
-
|
|
196
|
-
See [TypeScript Pattern](./examples.md#typescript-pattern) for a complete example showing how to use TypeScript with the context object.
|
|
197
|
-
|
|
198
|
-
## Error Handling Integration
|
|
199
|
-
|
|
200
|
-
Context objects are automatically passed to error handlers.
|
|
201
|
-
|
|
202
|
-
See [Error Handler Pattern](./examples.md#error-handler-pattern) for a complete example.
|
|
203
|
-
|
|
204
|
-
## Hook Integration
|
|
205
|
-
|
|
206
|
-
Context objects are passed to all hooks (before/after hooks).
|
|
207
|
-
|
|
208
|
-
See [Hook Pattern](./examples.md#hook-pattern) for a complete example.
|
|
209
|
-
|
|
210
|
-
## Security Considerations
|
|
211
|
-
|
|
212
|
-
YinzerFlow implements several security measures for context handling:
|
|
213
|
-
|
|
214
|
-
### 🛡️ Input Validation
|
|
215
|
-
- **Problem**: Malicious request data can cause injection attacks or bypass security controls
|
|
216
|
-
- **YinzerFlow Solution**: All request properties are automatically validated and sanitized
|
|
217
|
-
|
|
218
|
-
### 🛡️ Type Safety
|
|
219
|
-
- **Problem**: Untyped request data can lead to runtime errors and security vulnerabilities
|
|
220
|
-
- **YinzerFlow Solution**: Full TypeScript support with generic type parameters ensures type safety
|
|
221
|
-
|
|
222
|
-
### 🛡️ Context Isolation
|
|
223
|
-
- **Problem**: Context objects could be modified maliciously to bypass security controls
|
|
224
|
-
- **YinzerFlow Solution**: Context objects are isolated and modifications are controlled
|
|
225
|
-
|
|
226
|
-
### 🛡️ Header Security
|
|
227
|
-
- **Problem**: Malicious headers can cause parsing errors or security bypasses
|
|
228
|
-
- **YinzerFlow Solution**: Header validation and sanitization prevent header-based attacks
|
|
229
|
-
|
|
230
|
-
These security measures ensure YinzerFlow's context implementation follows security best practices and prevents common attack vectors while maintaining type safety and developer experience.
|
package/docs/core/examples.md
DELETED
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
# Common Examples
|
|
2
|
-
|
|
3
|
-
This file contains shared examples that are referenced throughout the YinzerFlow documentation to avoid duplication.
|
|
4
|
-
|
|
5
|
-
## Basic Handler Pattern
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { YinzerFlow } from 'yinzerflow';
|
|
9
|
-
|
|
10
|
-
const app = new YinzerFlow({ port: 3000 });
|
|
11
|
-
|
|
12
|
-
app.post('/api/users/:id', (ctx) => {
|
|
13
|
-
// Access request properties
|
|
14
|
-
const userId = ctx.request.params.id;
|
|
15
|
-
const userData = ctx.request.body;
|
|
16
|
-
const includeProfile = ctx.request.query.include_profile;
|
|
17
|
-
const authHeader = ctx.request.headers['authorization'];
|
|
18
|
-
const clientIp = ctx.request.ipAddress;
|
|
19
|
-
|
|
20
|
-
// Control response
|
|
21
|
-
ctx.response.setStatusCode(201);
|
|
22
|
-
ctx.response.addHeaders({
|
|
23
|
-
'Location': `/api/users/${userId}`,
|
|
24
|
-
'X-User-ID': userId
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
id: userId,
|
|
29
|
-
data: userData,
|
|
30
|
-
includeProfile: !!includeProfile,
|
|
31
|
-
clientIp
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Request Access Pattern
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
app.get('/api/users/:id', (ctx) => {
|
|
40
|
-
const { request } = ctx;
|
|
41
|
-
|
|
42
|
-
// Access route parameters
|
|
43
|
-
const userId = request.params.id;
|
|
44
|
-
|
|
45
|
-
// Access query parameters
|
|
46
|
-
const includeProfile = request.query.include_profile;
|
|
47
|
-
|
|
48
|
-
// Access headers
|
|
49
|
-
const contentType = request.headers['content-type'];
|
|
50
|
-
const authorization = request.headers['authorization'];
|
|
51
|
-
|
|
52
|
-
// Access request body
|
|
53
|
-
const userData = request.body;
|
|
54
|
-
|
|
55
|
-
// Access raw body for manual parsing when needed
|
|
56
|
-
const rawBody = request.rawBody;
|
|
57
|
-
|
|
58
|
-
const clientIp = request.ipAddress
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
message: 'Request processed successfully',
|
|
62
|
-
userId,
|
|
63
|
-
includeProfile,
|
|
64
|
-
contentType,
|
|
65
|
-
hasAuth: !!authorization,
|
|
66
|
-
receivedData: userData
|
|
67
|
-
};
|
|
68
|
-
});
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Response Control Pattern
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
app.get('/api/users/:id', (ctx) => {
|
|
75
|
-
const { request, response } = ctx;
|
|
76
|
-
const userId = request.params.id;
|
|
77
|
-
|
|
78
|
-
// Set successful status code
|
|
79
|
-
response.setStatusCode(200);
|
|
80
|
-
|
|
81
|
-
// Add custom headers
|
|
82
|
-
response.addHeaders({
|
|
83
|
-
'X-User-ID': userId,
|
|
84
|
-
'Cache-Control': 'max-age=3600',
|
|
85
|
-
'X-API-Version': '1.0'
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Return JSON response body (Content-Type automatically set)
|
|
89
|
-
return {
|
|
90
|
-
id: userId,
|
|
91
|
-
name: 'John Doe',
|
|
92
|
-
email: 'john@example.com',
|
|
93
|
-
timestamp: new Date().toISOString()
|
|
94
|
-
};
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Error Handler Pattern
|
|
99
|
-
|
|
100
|
-
## Context State Examples
|
|
101
|
-
|
|
102
|
-
### Basic State Usage
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
app.get('/api/users', async (ctx) => {
|
|
106
|
-
// Store simple data in state
|
|
107
|
-
ctx.state.requestId = generateRequestId();
|
|
108
|
-
ctx.state.timestamp = Date.now();
|
|
109
|
-
ctx.state.clientIp = ctx.request.ipAddress;
|
|
110
|
-
|
|
111
|
-
// Access the stored data
|
|
112
|
-
console.log(`Request ${ctx.state.requestId} from ${ctx.state.clientIp}`);
|
|
113
|
-
|
|
114
|
-
return { users: ['John', 'Jane'] };
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Authentication State Pattern
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// Authentication middleware
|
|
122
|
-
const authMiddleware: HandlerCallback = async (ctx) => {
|
|
123
|
-
const token = ctx.request.headers.authorization?.replace('Bearer ', '');
|
|
124
|
-
|
|
125
|
-
if (!token) {
|
|
126
|
-
throw new Error('Unauthorized');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
const user = await validateJWT(token);
|
|
131
|
-
const permissions = await getUserPermissions(user.id);
|
|
132
|
-
|
|
133
|
-
// Store authentication data in state
|
|
134
|
-
ctx.state.user = user;
|
|
135
|
-
ctx.state.permissions = permissions;
|
|
136
|
-
ctx.state.isAuthenticated = true;
|
|
137
|
-
ctx.state.authTimestamp = Date.now();
|
|
138
|
-
} catch (error) {
|
|
139
|
-
throw new Error('Invalid token');
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Protected route using the state
|
|
144
|
-
app.get('/api/admin/users', authMiddleware, async (ctx) => {
|
|
145
|
-
// Access the authenticated user data
|
|
146
|
-
const { user, permissions, isAuthenticated } = ctx.state;
|
|
147
|
-
|
|
148
|
-
if (!permissions.includes('admin')) {
|
|
149
|
-
throw new Error('Insufficient permissions');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
message: 'Admin access granted',
|
|
154
|
-
user: { id: user.id, name: user.name },
|
|
155
|
-
permissions,
|
|
156
|
-
authenticatedAt: ctx.state.authTimestamp
|
|
157
|
-
};
|
|
158
|
-
});
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Typed State Pattern
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
// Define your state interface
|
|
165
|
-
interface UserContext extends InternalHandlerCallbackGenerics {
|
|
166
|
-
state: {
|
|
167
|
-
user: User;
|
|
168
|
-
permissions: string[];
|
|
169
|
-
requestId: string;
|
|
170
|
-
session: {
|
|
171
|
-
id: string;
|
|
172
|
-
expiresAt: Date;
|
|
173
|
-
lastActivity: Date;
|
|
174
|
-
};
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Use typed state in your handler
|
|
179
|
-
const userProfileHandler: HandlerCallback<UserContext> = async (ctx) => {
|
|
180
|
-
// Fully type-safe access to state
|
|
181
|
-
const { user, permissions, session } = ctx.state;
|
|
182
|
-
|
|
183
|
-
// No type assertions needed!
|
|
184
|
-
if (session.expiresAt < new Date()) {
|
|
185
|
-
throw new Error('Session expired');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Update session activity
|
|
189
|
-
ctx.state.session.lastActivity = new Date();
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
profile: {
|
|
193
|
-
id: user.id,
|
|
194
|
-
name: user.name,
|
|
195
|
-
email: user.email,
|
|
196
|
-
permissions
|
|
197
|
-
},
|
|
198
|
-
session: {
|
|
199
|
-
id: session.id,
|
|
200
|
-
expiresAt: session.expiresAt,
|
|
201
|
-
lastActivity: ctx.state.session.lastActivity
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
};
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Middleware Chain State Pattern
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
// Rate limiting middleware
|
|
211
|
-
const rateLimitMiddleware: HandlerCallback = async (ctx) => {
|
|
212
|
-
const clientIp = ctx.request.ipAddress;
|
|
213
|
-
const currentCount = await getRequestCount(clientIp);
|
|
214
|
-
|
|
215
|
-
if (currentCount > 100) {
|
|
216
|
-
throw new Error('Rate limit exceeded');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Store rate limiting data in state
|
|
220
|
-
ctx.state.rateLimit = {
|
|
221
|
-
clientIp,
|
|
222
|
-
currentCount,
|
|
223
|
-
limit: 100,
|
|
224
|
-
resetTime: Date.now() + 60000 // 1 minute
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
await incrementRequestCount(clientIp);
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// Logging middleware
|
|
231
|
-
const loggingMiddleware: HandlerCallback = async (ctx) => {
|
|
232
|
-
// Access state from previous middleware
|
|
233
|
-
const rateLimit = ctx.state.rateLimit;
|
|
234
|
-
|
|
235
|
-
ctx.state.logData = {
|
|
236
|
-
requestId: generateRequestId(),
|
|
237
|
-
timestamp: Date.now(),
|
|
238
|
-
clientIp: ctx.request.ipAddress,
|
|
239
|
-
rateLimitInfo: rateLimit,
|
|
240
|
-
userAgent: ctx.request.headers['user-agent']
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
console.log('Request started:', ctx.state.logData);
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// Route using both middlewares
|
|
247
|
-
app.get('/api/data', rateLimitMiddleware, loggingMiddleware, async (ctx) => {
|
|
248
|
-
// Access state from all previous middleware
|
|
249
|
-
const { rateLimit, logData } = ctx.state;
|
|
250
|
-
|
|
251
|
-
// Add route-specific data to state
|
|
252
|
-
ctx.state.routeData = {
|
|
253
|
-
endpoint: '/api/data',
|
|
254
|
-
method: 'GET',
|
|
255
|
-
processingTime: Date.now() - logData.timestamp
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
return {
|
|
259
|
-
message: 'Data retrieved successfully',
|
|
260
|
-
rateLimit: {
|
|
261
|
-
remaining: rateLimit.limit - rateLimit.currentCount,
|
|
262
|
-
resetTime: rateLimit.resetTime
|
|
263
|
-
},
|
|
264
|
-
requestInfo: logData,
|
|
265
|
-
routeInfo: ctx.state.routeData
|
|
266
|
-
};
|
|
267
|
-
});
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Route Group State Pattern
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
app.group('/api/v1', (api) => {
|
|
274
|
-
// Global API state
|
|
275
|
-
api.beforeAll([async (ctx) => {
|
|
276
|
-
ctx.state.apiVersion = 'v1';
|
|
277
|
-
ctx.state.environment = process.env.NODE_ENV;
|
|
278
|
-
ctx.state.baseUrl = 'https://api.example.com';
|
|
279
|
-
}]);
|
|
280
|
-
|
|
281
|
-
api.group('/admin', (admin) => {
|
|
282
|
-
// Admin-specific state
|
|
283
|
-
admin.beforeAll([async (ctx) => {
|
|
284
|
-
ctx.state.requiresAuth = true;
|
|
285
|
-
ctx.state.adminOnly = true;
|
|
286
|
-
ctx.state.auditLog = true;
|
|
287
|
-
}]);
|
|
288
|
-
|
|
289
|
-
admin.group('/users', (users) => {
|
|
290
|
-
// User management state
|
|
291
|
-
users.beforeAll([async (ctx) => {
|
|
292
|
-
ctx.state.resourceType = 'user';
|
|
293
|
-
ctx.state.allowedOperations = ['create', 'read', 'update', 'delete'];
|
|
294
|
-
}]);
|
|
295
|
-
|
|
296
|
-
// Route inherits all state from parent groups
|
|
297
|
-
users.get('/', async (ctx) => {
|
|
298
|
-
const {
|
|
299
|
-
apiVersion, // "v1"
|
|
300
|
-
environment, // "production"
|
|
301
|
-
requiresAuth, // true
|
|
302
|
-
adminOnly, // true
|
|
303
|
-
auditLog, // true
|
|
304
|
-
resourceType, // "user"
|
|
305
|
-
allowedOperations // ["create", "read", "update", "delete"]
|
|
306
|
-
} = ctx.state;
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
message: 'User list retrieved',
|
|
310
|
-
apiInfo: { version: apiVersion, environment },
|
|
311
|
-
security: { requiresAuth, adminOnly, auditLog },
|
|
312
|
-
resource: { type: resourceType, operations: allowedOperations }
|
|
313
|
-
};
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### State Validation Pattern
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
// State validation helper
|
|
324
|
-
const validateState = (ctx: Context, requiredKeys: string[]) => {
|
|
325
|
-
const missing = requiredKeys.filter(key => !(key in ctx.state));
|
|
326
|
-
|
|
327
|
-
if (missing.length > 0) {
|
|
328
|
-
throw new Error(`Missing required state: ${missing.join(', ')}`);
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
// Middleware that sets required state
|
|
333
|
-
const userContextMiddleware: HandlerCallback = async (ctx) => {
|
|
334
|
-
const userId = ctx.request.params.userId;
|
|
335
|
-
|
|
336
|
-
if (!userId) {
|
|
337
|
-
throw new Error('User ID required');
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const user = await getUserById(userId);
|
|
341
|
-
if (!user) {
|
|
342
|
-
throw new Error('User not found');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Set required state
|
|
346
|
-
ctx.state.user = user;
|
|
347
|
-
ctx.state.userId = userId;
|
|
348
|
-
ctx.state.userPermissions = await getUserPermissions(userId);
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
// Route that validates state
|
|
352
|
-
app.get('/api/users/:userId/profile', userContextMiddleware, async (ctx) => {
|
|
353
|
-
// Validate required state
|
|
354
|
-
validateState(ctx, ['user', 'userId', 'userPermissions']);
|
|
355
|
-
|
|
356
|
-
// Now we can safely access state
|
|
357
|
-
const { user, userPermissions } = ctx.state;
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
profile: user,
|
|
361
|
-
permissions: userPermissions,
|
|
362
|
-
lastAccessed: new Date().toISOString()
|
|
363
|
-
};
|
|
364
|
-
});
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
## Hook Pattern
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
// Before hook
|
|
371
|
-
app.beforeAll([(ctx) => {
|
|
372
|
-
// Access request context
|
|
373
|
-
const authHeader = ctx.request.headers['authorization'];
|
|
374
|
-
|
|
375
|
-
// Validate authentication
|
|
376
|
-
if (!authHeader) {
|
|
377
|
-
throw new Error('Authentication required');
|
|
378
|
-
}
|
|
379
|
-
}]);
|
|
380
|
-
|
|
381
|
-
// After hook
|
|
382
|
-
app.afterAll([(ctx) => {
|
|
383
|
-
// Access response context
|
|
384
|
-
ctx.response.addHeaders({
|
|
385
|
-
'X-Response-Time': Date.now().toString()
|
|
386
|
-
});
|
|
387
|
-
}]);
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
## TypeScript Pattern
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
import type { HandlerCallback } from 'yinzerflow';
|
|
394
|
-
|
|
395
|
-
// Define custom types for your API
|
|
396
|
-
interface UserBody {
|
|
397
|
-
name: string;
|
|
398
|
-
email: string;
|
|
399
|
-
age: number;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
interface UserResponse {
|
|
403
|
-
id: string;
|
|
404
|
-
name: string;
|
|
405
|
-
email: string;
|
|
406
|
-
createdAt: string;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
interface UserQuery {
|
|
410
|
-
include_profile?: string;
|
|
411
|
-
limit?: string;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
interface UserParams {
|
|
415
|
-
id: string;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Typed handler with custom body, response, query, and params
|
|
419
|
-
const createUserHandler: HandlerCallback<{
|
|
420
|
-
body: UserBody;
|
|
421
|
-
response: UserResponse;
|
|
422
|
-
query: UserQuery;
|
|
423
|
-
params: UserParams;
|
|
424
|
-
}> = (ctx) => {
|
|
425
|
-
// ctx.request.body is typed as UserBody
|
|
426
|
-
const userData = ctx.request.body;
|
|
427
|
-
|
|
428
|
-
// ctx.request.query is typed as UserQuery
|
|
429
|
-
const includeProfile = ctx.request.query.include_profile;
|
|
430
|
-
|
|
431
|
-
// ctx.request.params is typed as UserParams
|
|
432
|
-
const userId = ctx.request.params.id;
|
|
433
|
-
|
|
434
|
-
// Return type is typed as UserResponse
|
|
435
|
-
return {
|
|
436
|
-
id: 'user-123',
|
|
437
|
-
name: userData.name,
|
|
438
|
-
email: userData.email,
|
|
439
|
-
createdAt: new Date().toISOString()
|
|
440
|
-
};
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
app.post('/api/users/:id', createUserHandler);
|
|
444
|
-
```
|