@riligar/agents-kit 1.10.0 → 1.12.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/.agent/skills/riligar-business-startup/SKILL.md +70 -0
- package/.agent/skills/{riligar-business-startup-analyst/SKILL.md → riligar-business-startup/references/business-case.md} +24 -251
- package/.agent/skills/riligar-business-startup/references/financial-model.md +215 -0
- package/.agent/skills/riligar-business-startup/references/market-analysis.md +151 -0
- package/.agent/skills/riligar-dev-backend/SKILL.md +122 -87
- package/.agent/skills/riligar-dev-backend/references/elysia-basics.md +224 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-lifecycle.md +268 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-patterns.md +324 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-plugins.md +202 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-validation.md +247 -0
- package/.agent/skills/riligar-dev-clean-code/SKILL.md +81 -133
- package/package.json +1 -1
- package/.agent/skills/riligar-business-startup-financial/SKILL.md +0 -391
- package/.agent/skills/riligar-business-startup-market/SKILL.md +0 -265
- package/.agent/skills/riligar-dev-backend/api-style.md +0 -42
- package/.agent/skills/riligar-dev-backend/auth.md +0 -24
- package/.agent/skills/riligar-dev-backend/documentation.md +0 -26
- package/.agent/skills/riligar-dev-backend/graphql.md +0 -41
- package/.agent/skills/riligar-dev-backend/rate-limiting.md +0 -31
- package/.agent/skills/riligar-dev-backend/response.md +0 -37
- package/.agent/skills/riligar-dev-backend/rest.md +0 -40
- package/.agent/skills/riligar-dev-backend/security-testing.md +0 -122
- package/.agent/skills/riligar-dev-backend/trpc.md +0 -41
- package/.agent/skills/riligar-dev-backend/versioning.md +0 -22
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Elysia Basics
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun add elysia
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Minimal Server
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
import { Elysia } from 'elysia'
|
|
13
|
+
|
|
14
|
+
const app = new Elysia()
|
|
15
|
+
.get('/', () => 'Hello World')
|
|
16
|
+
.listen(3000)
|
|
17
|
+
|
|
18
|
+
console.log(`Server running at ${app.server?.url}`)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## HTTP Methods
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
const app = new Elysia()
|
|
25
|
+
.get('/users', () => getUsers()) // Read
|
|
26
|
+
.post('/users', ({ body }) => create(body)) // Create
|
|
27
|
+
.put('/users/:id', ({ params, body }) => replace(params.id, body)) // Replace
|
|
28
|
+
.patch('/users/:id', ({ params, body }) => update(params.id, body)) // Update
|
|
29
|
+
.delete('/users/:id', ({ params }) => remove(params.id)) // Delete
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Context Object
|
|
33
|
+
|
|
34
|
+
Every handler receives a context object with request data:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
app.post('/example', (ctx) => {
|
|
38
|
+
// Request data
|
|
39
|
+
ctx.body // Parsed request body
|
|
40
|
+
ctx.query // Query string (?key=value)
|
|
41
|
+
ctx.params // Path parameters (/:id)
|
|
42
|
+
ctx.headers // Request headers
|
|
43
|
+
ctx.cookie // Cookies
|
|
44
|
+
ctx.request // Raw Request object
|
|
45
|
+
ctx.path // Request pathname
|
|
46
|
+
|
|
47
|
+
// Response helpers
|
|
48
|
+
ctx.set.status = 201 // Set status code
|
|
49
|
+
ctx.set.headers['X-Custom'] = 'value' // Set header
|
|
50
|
+
ctx.redirect('/other') // Redirect
|
|
51
|
+
|
|
52
|
+
return { success: true }
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Destructuring Context
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
// Clean handler with destructuring
|
|
60
|
+
app.post('/users', ({ body, set }) => {
|
|
61
|
+
const user = createUser(body)
|
|
62
|
+
set.status = 201
|
|
63
|
+
return user
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
app.get('/users/:id', ({ params }) => {
|
|
67
|
+
return getUserById(params.id)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
app.get('/search', ({ query }) => {
|
|
71
|
+
return search(query.q, query.limit)
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Response Types
|
|
76
|
+
|
|
77
|
+
### JSON (default)
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
app.get('/json', () => {
|
|
81
|
+
return { message: 'Hello' } // Automatically JSON
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### String
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
app.get('/text', () => {
|
|
89
|
+
return 'Plain text response'
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Status Codes
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
app.post('/users', ({ body, set }) => {
|
|
97
|
+
const user = createUser(body)
|
|
98
|
+
set.status = 201 // Created
|
|
99
|
+
return user
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
app.get('/users/:id', ({ params, set }) => {
|
|
103
|
+
const user = findUser(params.id)
|
|
104
|
+
if (!user) {
|
|
105
|
+
set.status = 404
|
|
106
|
+
return { error: 'User not found' }
|
|
107
|
+
}
|
|
108
|
+
return user
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Redirect
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
app.get('/old-path', ({ redirect }) => {
|
|
116
|
+
return redirect('/new-path')
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Route Parameters
|
|
121
|
+
|
|
122
|
+
### Path Parameters
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// Single parameter
|
|
126
|
+
app.get('/users/:id', ({ params }) => {
|
|
127
|
+
return getUserById(params.id)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Multiple parameters
|
|
131
|
+
app.get('/users/:userId/posts/:postId', ({ params }) => {
|
|
132
|
+
return getPost(params.userId, params.postId)
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Query Parameters
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
// GET /search?q=hello&limit=10
|
|
140
|
+
app.get('/search', ({ query }) => {
|
|
141
|
+
const { q, limit = '10' } = query
|
|
142
|
+
return search(q, parseInt(limit))
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Headers
|
|
147
|
+
|
|
148
|
+
### Reading Headers
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
app.get('/protected', ({ headers, set }) => {
|
|
152
|
+
const token = headers.authorization
|
|
153
|
+
if (!token) {
|
|
154
|
+
set.status = 401
|
|
155
|
+
return { error: 'Unauthorized' }
|
|
156
|
+
}
|
|
157
|
+
return { message: 'Welcome' }
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Setting Headers
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
app.get('/api/data', ({ set }) => {
|
|
165
|
+
set.headers['X-Custom-Header'] = 'value'
|
|
166
|
+
set.headers['Cache-Control'] = 'max-age=3600'
|
|
167
|
+
return { data: 'example' }
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Cookies
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
app.get('/set-cookie', ({ cookie }) => {
|
|
175
|
+
cookie.session.set({
|
|
176
|
+
value: 'abc123',
|
|
177
|
+
httpOnly: true,
|
|
178
|
+
maxAge: 60 * 60 * 24 // 1 day
|
|
179
|
+
})
|
|
180
|
+
return { message: 'Cookie set' }
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
app.get('/read-cookie', ({ cookie }) => {
|
|
184
|
+
return { session: cookie.session.value }
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Static Files
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
import { Elysia } from 'elysia'
|
|
192
|
+
import { staticPlugin } from '@elysiajs/static'
|
|
193
|
+
|
|
194
|
+
const app = new Elysia()
|
|
195
|
+
.use(staticPlugin({
|
|
196
|
+
prefix: '/public',
|
|
197
|
+
assets: 'public'
|
|
198
|
+
}))
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Environment Variables
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
// Use Bun's built-in env
|
|
205
|
+
const port = process.env.PORT || 3000
|
|
206
|
+
const dbUrl = process.env.DATABASE_URL
|
|
207
|
+
|
|
208
|
+
const app = new Elysia()
|
|
209
|
+
.listen(port)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Graceful Shutdown
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
const app = new Elysia()
|
|
216
|
+
.get('/', () => 'Hello')
|
|
217
|
+
.listen(3000)
|
|
218
|
+
|
|
219
|
+
process.on('SIGINT', () => {
|
|
220
|
+
console.log('Shutting down...')
|
|
221
|
+
app.stop()
|
|
222
|
+
process.exit(0)
|
|
223
|
+
})
|
|
224
|
+
```
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Elysia Lifecycle Hooks
|
|
2
|
+
|
|
3
|
+
Lifecycle hooks allow you to intercept requests at different stages. They're the foundation for middleware, auth, logging, and error handling.
|
|
4
|
+
|
|
5
|
+
## Lifecycle Order
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Request → onRequest → onParse → onBeforeHandle → Handler → onAfterHandle → onAfterResponse
|
|
9
|
+
↓
|
|
10
|
+
onError (if error thrown)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## onRequest
|
|
14
|
+
|
|
15
|
+
First hook executed. Minimal context, best for early checks.
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const app = new Elysia()
|
|
19
|
+
.onRequest(({ request }) => {
|
|
20
|
+
console.log(`${request.method} ${request.url}`)
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Use cases:**
|
|
25
|
+
- Request logging
|
|
26
|
+
- Rate limiting
|
|
27
|
+
- IP blocking
|
|
28
|
+
- CORS headers
|
|
29
|
+
|
|
30
|
+
**Early return skips remaining lifecycle:**
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
.onRequest(({ request, set }) => {
|
|
34
|
+
if (isBlocked(request)) {
|
|
35
|
+
set.status = 403
|
|
36
|
+
return { error: 'Forbidden' }
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## onBeforeHandle
|
|
42
|
+
|
|
43
|
+
Runs after validation, before handler. Has full context.
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
const app = new Elysia()
|
|
47
|
+
.onBeforeHandle(({ headers, set }) => {
|
|
48
|
+
if (!headers.authorization) {
|
|
49
|
+
set.status = 401
|
|
50
|
+
return { error: 'Unauthorized' }
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Use cases:**
|
|
56
|
+
- Authentication checks
|
|
57
|
+
- Authorization
|
|
58
|
+
- Custom validation
|
|
59
|
+
- Request transformation
|
|
60
|
+
|
|
61
|
+
## onAfterHandle
|
|
62
|
+
|
|
63
|
+
Runs after handler, can transform response.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const app = new Elysia()
|
|
67
|
+
.onAfterHandle(({ response, set }) => {
|
|
68
|
+
// Add header to all responses
|
|
69
|
+
set.headers['X-Powered-By'] = 'Elysia'
|
|
70
|
+
|
|
71
|
+
// Transform response
|
|
72
|
+
if (typeof response === 'object') {
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
data: response,
|
|
76
|
+
timestamp: Date.now()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Use cases:**
|
|
83
|
+
- Response transformation
|
|
84
|
+
- Adding headers
|
|
85
|
+
- Logging response
|
|
86
|
+
|
|
87
|
+
## onError
|
|
88
|
+
|
|
89
|
+
Catches any error thrown during lifecycle.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const app = new Elysia()
|
|
93
|
+
.onError(({ code, error, set }) => {
|
|
94
|
+
console.error(error)
|
|
95
|
+
|
|
96
|
+
switch (code) {
|
|
97
|
+
case 'VALIDATION':
|
|
98
|
+
set.status = 422
|
|
99
|
+
return { error: 'Validation failed', details: error.all }
|
|
100
|
+
|
|
101
|
+
case 'NOT_FOUND':
|
|
102
|
+
set.status = 404
|
|
103
|
+
return { error: 'Not found' }
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
set.status = 500
|
|
107
|
+
return { error: 'Internal server error' }
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Error codes:**
|
|
113
|
+
- `VALIDATION` - Schema validation failed
|
|
114
|
+
- `NOT_FOUND` - Route not found
|
|
115
|
+
- `PARSE` - Body parsing failed
|
|
116
|
+
- `INTERNAL_SERVER_ERROR` - Unhandled error
|
|
117
|
+
- `UNKNOWN` - Unknown error type
|
|
118
|
+
|
|
119
|
+
## onAfterResponse
|
|
120
|
+
|
|
121
|
+
Runs after response is sent. Cannot modify response.
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
const app = new Elysia()
|
|
125
|
+
.onAfterResponse(({ request, set }) => {
|
|
126
|
+
console.log(`Completed: ${request.method} ${request.url} - ${set.status}`)
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Use cases:**
|
|
131
|
+
- Logging
|
|
132
|
+
- Analytics
|
|
133
|
+
- Cleanup
|
|
134
|
+
|
|
135
|
+
## Local vs Interceptor Hooks
|
|
136
|
+
|
|
137
|
+
### Local Hook (single route)
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
app.get('/users', () => getUsers(), {
|
|
141
|
+
beforeHandle: ({ headers, set }) => {
|
|
142
|
+
if (!headers.authorization) {
|
|
143
|
+
set.status = 401
|
|
144
|
+
return { error: 'Unauthorized' }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Interceptor Hook (all subsequent routes)
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const app = new Elysia()
|
|
154
|
+
.onBeforeHandle(() => console.log('Runs on all routes below'))
|
|
155
|
+
.get('/a', () => 'A') // Hook runs
|
|
156
|
+
.get('/b', () => 'B') // Hook runs
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Practical Examples
|
|
160
|
+
|
|
161
|
+
### Authentication Middleware
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const requireAuth = ({ headers, set }) => {
|
|
165
|
+
const token = headers.authorization?.replace('Bearer ', '')
|
|
166
|
+
|
|
167
|
+
if (!token) {
|
|
168
|
+
set.status = 401
|
|
169
|
+
return { error: 'Missing token' }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const user = verifyToken(token)
|
|
174
|
+
// User available in handler
|
|
175
|
+
} catch {
|
|
176
|
+
set.status = 401
|
|
177
|
+
return { error: 'Invalid token' }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const app = new Elysia()
|
|
182
|
+
// Public routes
|
|
183
|
+
.get('/health', () => ({ status: 'ok' }))
|
|
184
|
+
|
|
185
|
+
// Protected routes
|
|
186
|
+
.guard({ beforeHandle: requireAuth }, app => app
|
|
187
|
+
.get('/profile', () => getProfile())
|
|
188
|
+
.put('/profile', ({ body }) => updateProfile(body))
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Request Logging
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const logger = new Elysia({ name: 'logger' })
|
|
196
|
+
.onRequest(({ request }) => {
|
|
197
|
+
const start = Date.now()
|
|
198
|
+
request.startTime = start
|
|
199
|
+
})
|
|
200
|
+
.onAfterResponse(({ request, set }) => {
|
|
201
|
+
const duration = Date.now() - request.startTime
|
|
202
|
+
console.log(`${request.method} ${request.url} ${set.status} ${duration}ms`)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
app.use(logger)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Error Wrapper
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
const app = new Elysia()
|
|
212
|
+
.onError(({ code, error, path, set }) => {
|
|
213
|
+
// Log error
|
|
214
|
+
console.error({
|
|
215
|
+
path,
|
|
216
|
+
code,
|
|
217
|
+
message: error.message,
|
|
218
|
+
stack: error.stack
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Don't expose internal errors
|
|
222
|
+
if (code === 'INTERNAL_SERVER_ERROR') {
|
|
223
|
+
set.status = 500
|
|
224
|
+
return {
|
|
225
|
+
error: 'Something went wrong',
|
|
226
|
+
requestId: generateRequestId()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Validation errors - expose details
|
|
231
|
+
if (code === 'VALIDATION') {
|
|
232
|
+
set.status = 422
|
|
233
|
+
return {
|
|
234
|
+
error: 'Validation failed',
|
|
235
|
+
details: error.all
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Rate Limiting
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
const rateLimits = new Map()
|
|
245
|
+
|
|
246
|
+
const rateLimit = (limit = 100, windowMs = 60000) => {
|
|
247
|
+
return new Elysia({ name: 'rate-limit' })
|
|
248
|
+
.onRequest(({ request, set }) => {
|
|
249
|
+
const ip = request.headers.get('x-forwarded-for') || 'unknown'
|
|
250
|
+
const now = Date.now()
|
|
251
|
+
const windowStart = now - windowMs
|
|
252
|
+
|
|
253
|
+
const requests = rateLimits.get(ip) || []
|
|
254
|
+
const recentRequests = requests.filter(t => t > windowStart)
|
|
255
|
+
|
|
256
|
+
if (recentRequests.length >= limit) {
|
|
257
|
+
set.status = 429
|
|
258
|
+
set.headers['Retry-After'] = Math.ceil(windowMs / 1000)
|
|
259
|
+
return { error: 'Too many requests' }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
recentRequests.push(now)
|
|
263
|
+
rateLimits.set(ip, recentRequests)
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
app.use(rateLimit(100, 60000)) // 100 requests per minute
|
|
268
|
+
```
|