@spfn/core 0.2.0-beta.5 → 0.2.0-beta.8
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 +260 -1175
- package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
- package/dist/codegen/index.d.ts +47 -2
- package/dist/codegen/index.js +143 -5
- package/dist/codegen/index.js.map +1 -1
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.js +40 -6
- package/dist/db/index.js.map +1 -1
- package/dist/job/index.d.ts +23 -8
- package/dist/job/index.js +43 -3
- package/dist/job/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +35 -3
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +61 -14
- package/dist/nextjs/server.js +98 -32
- package/dist/nextjs/server.js.map +1 -1
- package/dist/route/index.d.ts +136 -2
- package/dist/route/index.js +209 -11
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +72 -1
- package/dist/server/index.js +41 -0
- package/dist/server/index.js.map +1 -1
- package/dist/{types-D_N_U-Py.d.ts → types-BOPTApC2.d.ts} +15 -0
- package/docs/cache.md +133 -0
- package/docs/codegen.md +74 -0
- package/docs/database.md +346 -0
- package/docs/entity.md +539 -0
- package/docs/env.md +477 -0
- package/docs/errors.md +319 -0
- package/docs/event.md +116 -0
- package/docs/file-upload.md +717 -0
- package/docs/job.md +131 -0
- package/docs/logger.md +108 -0
- package/docs/middleware.md +337 -0
- package/docs/nextjs.md +241 -0
- package/docs/repository.md +496 -0
- package/docs/route.md +497 -0
- package/docs/server.md +307 -0
- package/package.json +1 -1
package/docs/job.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Job
|
|
2
|
+
|
|
3
|
+
Background job processing.
|
|
4
|
+
|
|
5
|
+
## Define Jobs
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// src/server/jobs/send-email.job.ts
|
|
9
|
+
import { defineJob } from '@spfn/core/job';
|
|
10
|
+
|
|
11
|
+
export const sendEmailJob = defineJob<{
|
|
12
|
+
to: string;
|
|
13
|
+
subject: string;
|
|
14
|
+
body: string;
|
|
15
|
+
}>({
|
|
16
|
+
name: 'send-email',
|
|
17
|
+
handler: async (payload) => {
|
|
18
|
+
await emailService.send({
|
|
19
|
+
to: payload.to,
|
|
20
|
+
subject: payload.subject,
|
|
21
|
+
body: payload.body
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Enqueue Jobs
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { enqueue } from '@spfn/core/job';
|
|
31
|
+
import { sendEmailJob } from './jobs/send-email.job';
|
|
32
|
+
|
|
33
|
+
// Enqueue for immediate processing
|
|
34
|
+
await enqueue(sendEmailJob, {
|
|
35
|
+
to: 'user@example.com',
|
|
36
|
+
subject: 'Welcome',
|
|
37
|
+
body: 'Welcome to our app!'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Enqueue with delay
|
|
41
|
+
await enqueue(sendEmailJob, payload, {
|
|
42
|
+
delay: 60000 // 1 minute
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Enqueue with options
|
|
46
|
+
await enqueue(sendEmailJob, payload, {
|
|
47
|
+
priority: 'high',
|
|
48
|
+
attempts: 3,
|
|
49
|
+
backoff: 'exponential'
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Job Options
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
defineJob({
|
|
57
|
+
name: 'process-image',
|
|
58
|
+
concurrency: 5, // Max concurrent jobs
|
|
59
|
+
attempts: 3, // Retry attempts
|
|
60
|
+
backoff: 'exponential', // Backoff strategy
|
|
61
|
+
timeout: 30000, // Job timeout (ms)
|
|
62
|
+
handler: async (payload) => {
|
|
63
|
+
// ...
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Scheduled Jobs
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { schedule } from '@spfn/core/job';
|
|
72
|
+
|
|
73
|
+
// Run every hour
|
|
74
|
+
schedule('cleanup', '0 * * * *', async () => {
|
|
75
|
+
await cleanupExpiredSessions();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Run daily at midnight
|
|
79
|
+
schedule('daily-report', '0 0 * * *', async () => {
|
|
80
|
+
await generateDailyReport();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Run every 5 minutes
|
|
84
|
+
schedule('health-check', '*/5 * * * *', async () => {
|
|
85
|
+
await checkExternalServices();
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Job Registration
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// src/server/jobs/index.ts
|
|
93
|
+
import { registerJobs } from '@spfn/core/job';
|
|
94
|
+
import { sendEmailJob } from './send-email.job';
|
|
95
|
+
import { processImageJob } from './process-image.job';
|
|
96
|
+
|
|
97
|
+
export function initializeJobs()
|
|
98
|
+
{
|
|
99
|
+
registerJobs([
|
|
100
|
+
sendEmailJob,
|
|
101
|
+
processImageJob
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Best Practices
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// 1. Keep jobs idempotent
|
|
110
|
+
handler: async (payload) => {
|
|
111
|
+
// Check if already processed
|
|
112
|
+
const existing = await db.findProcessed(payload.id);
|
|
113
|
+
if (existing) return;
|
|
114
|
+
|
|
115
|
+
await processItem(payload);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 2. Use appropriate timeout
|
|
119
|
+
timeout: 30000 // Don't set too high
|
|
120
|
+
|
|
121
|
+
// 3. Handle failures gracefully
|
|
122
|
+
attempts: 3,
|
|
123
|
+
backoff: 'exponential'
|
|
124
|
+
|
|
125
|
+
// 4. Log job progress
|
|
126
|
+
handler: async (payload) => {
|
|
127
|
+
logger.info('Processing job', { jobId: payload.id });
|
|
128
|
+
// ...
|
|
129
|
+
logger.info('Job completed', { jobId: payload.id });
|
|
130
|
+
}
|
|
131
|
+
```
|
package/docs/logger.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Logger
|
|
2
|
+
|
|
3
|
+
Structured logging with context support.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { logger } from '@spfn/core/logger';
|
|
9
|
+
|
|
10
|
+
logger.info('User created', { userId: '123' });
|
|
11
|
+
logger.warn('Rate limit approaching', { remaining: 10 });
|
|
12
|
+
logger.error('Failed to process', { error: err.message });
|
|
13
|
+
logger.debug('Processing request', { path: '/api/users' });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Log Levels
|
|
17
|
+
|
|
18
|
+
| Level | Usage |
|
|
19
|
+
|-------|-------|
|
|
20
|
+
| `debug` | Development debugging |
|
|
21
|
+
| `info` | General information |
|
|
22
|
+
| `warn` | Warning conditions |
|
|
23
|
+
| `error` | Error conditions |
|
|
24
|
+
|
|
25
|
+
## Structured Logging
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Good - structured data
|
|
29
|
+
logger.info('User login', {
|
|
30
|
+
userId: user.id,
|
|
31
|
+
email: user.email,
|
|
32
|
+
ip: request.ip
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Bad - string concatenation
|
|
36
|
+
logger.info(`User ${user.id} logged in from ${request.ip}`);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Create Scoped Logger
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { createLogger } from '@spfn/core/logger';
|
|
43
|
+
|
|
44
|
+
const userLogger = createLogger('user-service');
|
|
45
|
+
|
|
46
|
+
userLogger.info('Created user');
|
|
47
|
+
// Output: [user-service] Created user
|
|
48
|
+
|
|
49
|
+
const paymentLogger = createLogger('payment');
|
|
50
|
+
paymentLogger.error('Payment failed', { orderId: '123' });
|
|
51
|
+
// Output: [payment] Payment failed { orderId: '123' }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Context Logging
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { withLogContext } from '@spfn/core/logger';
|
|
58
|
+
|
|
59
|
+
// Add context to all logs in scope
|
|
60
|
+
await withLogContext({ requestId: '123', userId: 'abc' }, async () => {
|
|
61
|
+
logger.info('Processing request');
|
|
62
|
+
// Output includes: { requestId: '123', userId: 'abc' }
|
|
63
|
+
|
|
64
|
+
await doSomething();
|
|
65
|
+
logger.info('Request complete');
|
|
66
|
+
// Also includes context
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Log Format
|
|
71
|
+
|
|
72
|
+
Development (pretty):
|
|
73
|
+
```
|
|
74
|
+
2024-01-15 10:30:45 INFO User created { userId: '123', email: 'user@example.com' }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Production (JSON):
|
|
78
|
+
```json
|
|
79
|
+
{"timestamp":"2024-01-15T10:30:45.123Z","level":"info","message":"User created","userId":"123","email":"user@example.com"}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Best Practices
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// 1. Use structured data
|
|
86
|
+
logger.info('Operation complete', { duration: 150, result: 'success' });
|
|
87
|
+
|
|
88
|
+
// 2. Include error details
|
|
89
|
+
logger.error('Request failed', {
|
|
90
|
+
error: err.message,
|
|
91
|
+
stack: err.stack,
|
|
92
|
+
path: req.path
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 3. Use appropriate levels
|
|
96
|
+
logger.debug(...) // Development only
|
|
97
|
+
logger.info(...) // Normal operations
|
|
98
|
+
logger.warn(...) // Potential issues
|
|
99
|
+
logger.error(...) // Errors requiring attention
|
|
100
|
+
|
|
101
|
+
// 4. Create scoped loggers for modules
|
|
102
|
+
const dbLogger = createLogger('database');
|
|
103
|
+
const authLogger = createLogger('auth');
|
|
104
|
+
|
|
105
|
+
// 5. Don't log sensitive data
|
|
106
|
+
logger.info('User login', { userId: '123' }); // Good
|
|
107
|
+
logger.info('User login', { password: '...' }); // Bad!
|
|
108
|
+
```
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Middleware
|
|
2
|
+
|
|
3
|
+
Named middleware system with route-level skip control.
|
|
4
|
+
|
|
5
|
+
## Define Middleware
|
|
6
|
+
|
|
7
|
+
### Regular Middleware
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { defineMiddleware } from '@spfn/core/route';
|
|
11
|
+
|
|
12
|
+
export const authMiddleware = defineMiddleware('auth', async (c, next) => {
|
|
13
|
+
const token = c.req.header('authorization');
|
|
14
|
+
|
|
15
|
+
if (!token)
|
|
16
|
+
{
|
|
17
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const user = await verifyToken(token);
|
|
21
|
+
c.set('user', user);
|
|
22
|
+
|
|
23
|
+
await next();
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Factory Middleware
|
|
28
|
+
|
|
29
|
+
Middleware with parameters:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export const requireRole = defineMiddleware('role',
|
|
33
|
+
(...roles: string[]) => async (c, next) => {
|
|
34
|
+
const user = c.get('user');
|
|
35
|
+
|
|
36
|
+
if (!roles.includes(user.role))
|
|
37
|
+
{
|
|
38
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await next();
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Usage
|
|
46
|
+
route.get('/admin')
|
|
47
|
+
.use([requireRole('admin', 'superadmin')])
|
|
48
|
+
.handler(...)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Two-Parameter Factory
|
|
52
|
+
|
|
53
|
+
For factory with exactly 2 parameters, use `defineMiddlewareFactory`:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { defineMiddlewareFactory } from '@spfn/core/route';
|
|
57
|
+
|
|
58
|
+
export const rateLimiter = defineMiddlewareFactory('rateLimit',
|
|
59
|
+
(limit: number, windowMs: number) => async (c, next) => {
|
|
60
|
+
// Rate limit logic
|
|
61
|
+
await next();
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Usage
|
|
66
|
+
route.get('/api')
|
|
67
|
+
.use([rateLimiter(100, 60000)]) // 100 requests per minute
|
|
68
|
+
.handler(...)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Register Global Middleware
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// src/server/server.config.ts
|
|
77
|
+
import { defineServerConfig } from '@spfn/core/server';
|
|
78
|
+
import { authMiddleware, loggerMiddleware, corsMiddleware } from './middlewares';
|
|
79
|
+
|
|
80
|
+
export default defineServerConfig()
|
|
81
|
+
.middlewares([
|
|
82
|
+
loggerMiddleware,
|
|
83
|
+
corsMiddleware,
|
|
84
|
+
authMiddleware
|
|
85
|
+
])
|
|
86
|
+
.routes(appRouter)
|
|
87
|
+
.build();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Execution order:**
|
|
91
|
+
1. Global middlewares (in registration order)
|
|
92
|
+
2. Route-level middlewares (from `.use()`)
|
|
93
|
+
3. Validation middleware (automatic)
|
|
94
|
+
4. Route handler
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Use in Routes
|
|
99
|
+
|
|
100
|
+
### Add Middleware
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { Transactional } from '@spfn/core/db';
|
|
104
|
+
import { authMiddleware, requireRole } from './middlewares';
|
|
105
|
+
|
|
106
|
+
route.post('/admin/users')
|
|
107
|
+
.use([authMiddleware, requireRole('admin'), Transactional()])
|
|
108
|
+
.handler(async (c) => {
|
|
109
|
+
const user = c.raw.get('user'); // From authMiddleware
|
|
110
|
+
// ...
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Skip Global Middleware
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Skip specific middlewares by name
|
|
118
|
+
route.get('/public/health')
|
|
119
|
+
.skip(['auth', 'rateLimit'])
|
|
120
|
+
.handler(async (c) => {
|
|
121
|
+
return { status: 'ok' };
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Skip all global middlewares
|
|
125
|
+
route.get('/webhooks/stripe')
|
|
126
|
+
.skip('*')
|
|
127
|
+
.handler(async (c) => {
|
|
128
|
+
// No global middleware applied
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Note:** Route-level middlewares (`.use()`) are never skipped.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Common Middleware Patterns
|
|
137
|
+
|
|
138
|
+
### Authentication
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
export const authMiddleware = defineMiddleware('auth', async (c, next) => {
|
|
142
|
+
const token = c.req.header('authorization')?.replace('Bearer ', '');
|
|
143
|
+
|
|
144
|
+
if (!token)
|
|
145
|
+
{
|
|
146
|
+
return c.json({ error: 'Missing token' }, 401);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try
|
|
150
|
+
{
|
|
151
|
+
const payload = await verifyJWT(token);
|
|
152
|
+
c.set('userId', payload.sub);
|
|
153
|
+
c.set('user', await userRepo.findById(payload.sub));
|
|
154
|
+
await next();
|
|
155
|
+
}
|
|
156
|
+
catch
|
|
157
|
+
{
|
|
158
|
+
return c.json({ error: 'Invalid token' }, 401);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Role-based Access
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
export const requirePermissions = defineMiddleware('permission',
|
|
167
|
+
(...permissions: string[]) => async (c, next) => {
|
|
168
|
+
const user = c.get('user');
|
|
169
|
+
|
|
170
|
+
const hasPermission = permissions.every(p =>
|
|
171
|
+
user.permissions.includes(p)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!hasPermission)
|
|
175
|
+
{
|
|
176
|
+
return c.json({ error: 'Insufficient permissions' }, 403);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await next();
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Usage
|
|
184
|
+
route.delete('/posts/:id')
|
|
185
|
+
.use([requirePermissions('posts:delete')])
|
|
186
|
+
.handler(...)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Rate Limiting
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const requestCounts = new Map<string, { count: number; resetAt: number }>();
|
|
193
|
+
|
|
194
|
+
export const rateLimiter = defineMiddlewareFactory('rateLimit',
|
|
195
|
+
(limit: number, windowMs: number) => async (c, next) => {
|
|
196
|
+
const key = c.req.header('x-forwarded-for') || 'unknown';
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
|
|
199
|
+
const record = requestCounts.get(key);
|
|
200
|
+
|
|
201
|
+
if (!record || now > record.resetAt)
|
|
202
|
+
{
|
|
203
|
+
requestCounts.set(key, { count: 1, resetAt: now + windowMs });
|
|
204
|
+
}
|
|
205
|
+
else if (record.count >= limit)
|
|
206
|
+
{
|
|
207
|
+
return c.json({ error: 'Too many requests' }, 429);
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
{
|
|
211
|
+
record.count++;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await next();
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Request Logging
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
export const requestLogger = defineMiddleware('requestLogger', async (c, next) => {
|
|
223
|
+
const start = Date.now();
|
|
224
|
+
const method = c.req.method;
|
|
225
|
+
const path = c.req.path;
|
|
226
|
+
|
|
227
|
+
await next();
|
|
228
|
+
|
|
229
|
+
const duration = Date.now() - start;
|
|
230
|
+
const status = c.res.status;
|
|
231
|
+
|
|
232
|
+
console.log(`${method} ${path} ${status} ${duration}ms`);
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### CORS
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
export const corsMiddleware = defineMiddleware('cors', async (c, next) => {
|
|
240
|
+
const origin = c.req.header('origin');
|
|
241
|
+
|
|
242
|
+
if (origin && allowedOrigins.includes(origin))
|
|
243
|
+
{
|
|
244
|
+
c.header('Access-Control-Allow-Origin', origin);
|
|
245
|
+
c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
|
|
246
|
+
c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (c.req.method === 'OPTIONS')
|
|
250
|
+
{
|
|
251
|
+
return c.body(null, 204);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await next();
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Middleware Deduplication
|
|
261
|
+
|
|
262
|
+
When the same middleware is registered both globally and in a route, it's automatically deduplicated:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Registered globally
|
|
266
|
+
.middlewares([authMiddleware])
|
|
267
|
+
|
|
268
|
+
// Also used in route
|
|
269
|
+
route.get('/users')
|
|
270
|
+
.use([authMiddleware]) // Skipped (already applied globally)
|
|
271
|
+
.handler(...)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Access Context Data
|
|
277
|
+
|
|
278
|
+
### Set Data
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
defineMiddleware('auth', async (c, next) => {
|
|
282
|
+
c.set('user', user);
|
|
283
|
+
c.set('sessionId', sessionId);
|
|
284
|
+
await next();
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Get Data in Handler
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
route.get('/profile')
|
|
292
|
+
.handler(async (c) => {
|
|
293
|
+
const user = c.raw.get('user');
|
|
294
|
+
const sessionId = c.raw.get('sessionId');
|
|
295
|
+
// ...
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Best Practices
|
|
302
|
+
|
|
303
|
+
### Do
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// 1. Use meaningful names for skip control
|
|
307
|
+
export const authMiddleware = defineMiddleware('auth', ...);
|
|
308
|
+
export const rateLimiter = defineMiddleware('rateLimit', ...);
|
|
309
|
+
|
|
310
|
+
// 2. Set context data for downstream use
|
|
311
|
+
c.set('user', user);
|
|
312
|
+
|
|
313
|
+
// 3. Return early for unauthorized requests
|
|
314
|
+
if (!token) return c.json({ error: 'Unauthorized' }, 401);
|
|
315
|
+
|
|
316
|
+
// 4. Always call next() for successful middleware
|
|
317
|
+
await next();
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Don't
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// 1. Don't forget to call next()
|
|
324
|
+
defineMiddleware('logger', async (c, next) => {
|
|
325
|
+
console.log(c.req.path);
|
|
326
|
+
// Missing next() - request hangs!
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// 2. Don't use generic names
|
|
330
|
+
defineMiddleware('middleware1', ...); // Bad
|
|
331
|
+
|
|
332
|
+
// 3. Don't throw errors - return responses
|
|
333
|
+
defineMiddleware('auth', async (c, next) => {
|
|
334
|
+
if (!token) throw new Error('No token'); // Bad
|
|
335
|
+
if (!token) return c.json({ error: 'No token' }, 401); // Good
|
|
336
|
+
});
|
|
337
|
+
```
|