@riligar/agents-kit 1.11.0 → 1.13.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-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-stripe/SKILL.md +196 -91
- package/.agent/skills/riligar-dev-stripe/assets/stripe-client.js +422 -0
- package/.agent/skills/riligar-dev-stripe/assets/stripe-server.js +300 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-database.md +369 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-elysia.md +342 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-react.md +478 -0
- package/.agent/skills/riligar-dev-stripe/references/stripe-webhooks.md +376 -0
- package/package.json +1 -1
- 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,202 @@
|
|
|
1
|
+
# Elysia Plugins
|
|
2
|
+
|
|
3
|
+
Plugins are the core pattern for organizing Elysia applications. Each plugin is a self-contained Elysia instance that can be composed into larger applications.
|
|
4
|
+
|
|
5
|
+
## Creating a Plugin
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// routes/users.js
|
|
9
|
+
import { Elysia } from 'elysia'
|
|
10
|
+
|
|
11
|
+
export const userRoutes = new Elysia({ prefix: '/users' })
|
|
12
|
+
.get('/', () => getAllUsers())
|
|
13
|
+
.get('/:id', ({ params }) => getUserById(params.id))
|
|
14
|
+
.post('/', ({ body }) => createUser(body))
|
|
15
|
+
.delete('/:id', ({ params }) => deleteUser(params.id))
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Using Plugins
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// index.js
|
|
22
|
+
import { Elysia } from 'elysia'
|
|
23
|
+
import { userRoutes } from './routes/users'
|
|
24
|
+
import { postRoutes } from './routes/posts'
|
|
25
|
+
|
|
26
|
+
const app = new Elysia()
|
|
27
|
+
.use(userRoutes)
|
|
28
|
+
.use(postRoutes)
|
|
29
|
+
.listen(3000)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Plugin with Configuration
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
// plugins/logger.js
|
|
36
|
+
export const loggerPlugin = (options = {}) => {
|
|
37
|
+
const { prefix = '[LOG]' } = options
|
|
38
|
+
|
|
39
|
+
return new Elysia({ name: 'logger' })
|
|
40
|
+
.onRequest(({ request }) => {
|
|
41
|
+
console.log(`${prefix} ${request.method} ${request.url}`)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Usage
|
|
46
|
+
app.use(loggerPlugin({ prefix: '[API]' }))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Named Plugins (Deduplication)
|
|
50
|
+
|
|
51
|
+
Use `name` to prevent duplicate plugin registration:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
const dbPlugin = new Elysia({ name: 'database' })
|
|
55
|
+
.decorate('db', database)
|
|
56
|
+
|
|
57
|
+
// Even if used multiple times, only registers once
|
|
58
|
+
app.use(dbPlugin).use(dbPlugin)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Decorators (Dependency Injection)
|
|
62
|
+
|
|
63
|
+
Add custom properties to the context:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const app = new Elysia()
|
|
67
|
+
.decorate('db', database)
|
|
68
|
+
.decorate('config', { apiKey: process.env.API_KEY })
|
|
69
|
+
.get('/users', ({ db }) => db.query.users.findMany())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## State (Global Mutable State)
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
const app = new Elysia()
|
|
76
|
+
.state('counter', 0)
|
|
77
|
+
.get('/count', ({ store }) => {
|
|
78
|
+
store.counter++
|
|
79
|
+
return { count: store.counter }
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Guard Pattern
|
|
84
|
+
|
|
85
|
+
Apply validation/hooks to multiple routes:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
const app = new Elysia()
|
|
89
|
+
.guard({
|
|
90
|
+
beforeHandle: ({ headers, set }) => {
|
|
91
|
+
if (!headers.authorization) {
|
|
92
|
+
set.status = 401
|
|
93
|
+
return { error: 'Unauthorized' }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}, app => app
|
|
97
|
+
.get('/profile', () => getProfile())
|
|
98
|
+
.get('/settings', () => getSettings())
|
|
99
|
+
.put('/settings', ({ body }) => updateSettings(body))
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Group with Prefix
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
const app = new Elysia()
|
|
107
|
+
.group('/api/v1', app => app
|
|
108
|
+
.get('/users', () => getUsers())
|
|
109
|
+
.get('/posts', () => getPosts())
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Routes: /api/v1/users, /api/v1/posts
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Combining Guard + Group
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
const app = new Elysia()
|
|
119
|
+
.group('/api/v1', app => app
|
|
120
|
+
// Public routes
|
|
121
|
+
.get('/health', () => ({ status: 'ok' }))
|
|
122
|
+
|
|
123
|
+
// Protected routes
|
|
124
|
+
.guard({
|
|
125
|
+
beforeHandle: requireAuth
|
|
126
|
+
}, app => app
|
|
127
|
+
.get('/profile', () => getProfile())
|
|
128
|
+
.put('/profile', ({ body }) => updateProfile(body))
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Plugin Scope
|
|
134
|
+
|
|
135
|
+
### Local Scope (Default)
|
|
136
|
+
|
|
137
|
+
Hooks only apply to the plugin and its children:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const plugin = new Elysia()
|
|
141
|
+
.onBeforeHandle(() => console.log('Only in this plugin'))
|
|
142
|
+
.get('/local', () => 'Hello')
|
|
143
|
+
|
|
144
|
+
app.use(plugin)
|
|
145
|
+
app.get('/other', () => 'No hook here')
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Global Scope
|
|
149
|
+
|
|
150
|
+
Hooks apply to all routes:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const globalLogger = new Elysia()
|
|
154
|
+
.onBeforeHandle({ as: 'global' }, () => {
|
|
155
|
+
console.log('Runs on ALL routes')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
app.use(globalLogger)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Recommended Structure
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
src/
|
|
165
|
+
├── index.js # Main app
|
|
166
|
+
├── routes/
|
|
167
|
+
│ ├── index.js # Route aggregator
|
|
168
|
+
│ ├── users.js # User routes plugin
|
|
169
|
+
│ ├── posts.js # Post routes plugin
|
|
170
|
+
│ └── auth.js # Auth routes plugin
|
|
171
|
+
├── plugins/
|
|
172
|
+
│ ├── database.js # DB connection plugin
|
|
173
|
+
│ ├── logger.js # Logging plugin
|
|
174
|
+
│ └── cors.js # CORS plugin
|
|
175
|
+
├── services/
|
|
176
|
+
│ ├── user.js # User business logic
|
|
177
|
+
│ └── post.js # Post business logic
|
|
178
|
+
└── middleware/
|
|
179
|
+
└── auth.js # Auth middleware
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Route Aggregator Example
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// routes/index.js
|
|
186
|
+
import { Elysia } from 'elysia'
|
|
187
|
+
import { userRoutes } from './users'
|
|
188
|
+
import { postRoutes } from './posts'
|
|
189
|
+
import { authRoutes } from './auth'
|
|
190
|
+
|
|
191
|
+
export const routes = new Elysia()
|
|
192
|
+
.use(authRoutes)
|
|
193
|
+
.use(userRoutes)
|
|
194
|
+
.use(postRoutes)
|
|
195
|
+
|
|
196
|
+
// index.js
|
|
197
|
+
import { routes } from './routes'
|
|
198
|
+
|
|
199
|
+
const app = new Elysia()
|
|
200
|
+
.use(routes)
|
|
201
|
+
.listen(3000)
|
|
202
|
+
```
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Elysia Validation
|
|
2
|
+
|
|
3
|
+
Elysia uses TypeBox for runtime validation. Import `t` from Elysia to define schemas.
|
|
4
|
+
|
|
5
|
+
## Basic Validation
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { Elysia, t } from 'elysia'
|
|
9
|
+
|
|
10
|
+
const app = new Elysia()
|
|
11
|
+
.post('/users', ({ body }) => createUser(body), {
|
|
12
|
+
body: t.Object({
|
|
13
|
+
name: t.String(),
|
|
14
|
+
email: t.String()
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Validation Targets
|
|
20
|
+
|
|
21
|
+
### Body Validation
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
app.post('/users', ({ body }) => body, {
|
|
25
|
+
body: t.Object({
|
|
26
|
+
name: t.String(),
|
|
27
|
+
email: t.String({ format: 'email' }),
|
|
28
|
+
age: t.Number({ minimum: 0 })
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Query Validation
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// GET /search?q=hello&limit=10
|
|
37
|
+
app.get('/search', ({ query }) => search(query), {
|
|
38
|
+
query: t.Object({
|
|
39
|
+
q: t.String(),
|
|
40
|
+
limit: t.Optional(t.Numeric({ default: 10 }))
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Params Validation
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
app.get('/users/:id', ({ params }) => getUser(params.id), {
|
|
49
|
+
params: t.Object({
|
|
50
|
+
id: t.Numeric()
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Headers Validation
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
app.get('/protected', ({ headers }) => getData(), {
|
|
59
|
+
headers: t.Object({
|
|
60
|
+
authorization: t.String()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## TypeBox Types
|
|
66
|
+
|
|
67
|
+
### Primitives
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
t.String() // string
|
|
71
|
+
t.Number() // number
|
|
72
|
+
t.Boolean() // boolean
|
|
73
|
+
t.Null() // null
|
|
74
|
+
t.Literal('active') // exact value
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### String Constraints
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
t.String({ minLength: 1 })
|
|
81
|
+
t.String({ maxLength: 100 })
|
|
82
|
+
t.String({ format: 'email' })
|
|
83
|
+
t.String({ format: 'uuid' })
|
|
84
|
+
t.String({ pattern: '^[a-z]+$' }) // regex
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Number Constraints
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
t.Number({ minimum: 0 })
|
|
91
|
+
t.Number({ maximum: 100 })
|
|
92
|
+
t.Number({ exclusiveMinimum: 0 })
|
|
93
|
+
t.Integer() // whole numbers only
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Numeric (String to Number)
|
|
97
|
+
|
|
98
|
+
For query params that come as strings:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
t.Numeric() // Converts "10" to 10
|
|
102
|
+
t.Numeric({ minimum: 1, maximum: 100 })
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Optional & Nullable
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
t.Optional(t.String()) // string | undefined
|
|
109
|
+
t.Nullable(t.String()) // string | null
|
|
110
|
+
t.Optional(t.Nullable(t.String())) // string | null | undefined
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Default Values
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
t.String({ default: 'guest' })
|
|
117
|
+
t.Number({ default: 10 })
|
|
118
|
+
t.Boolean({ default: false })
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Objects
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
t.Object({
|
|
125
|
+
name: t.String(),
|
|
126
|
+
email: t.String(),
|
|
127
|
+
profile: t.Optional(t.Object({
|
|
128
|
+
bio: t.String(),
|
|
129
|
+
avatar: t.String()
|
|
130
|
+
}))
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Arrays
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
t.Array(t.String()) // string[]
|
|
138
|
+
t.Array(t.Number(), { minItems: 1 }) // at least 1 item
|
|
139
|
+
t.Array(t.Object({ id: t.Number() })) // object array
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Union (One of)
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
t.Union([
|
|
146
|
+
t.Literal('active'),
|
|
147
|
+
t.Literal('inactive'),
|
|
148
|
+
t.Literal('pending')
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
// Or with different types
|
|
152
|
+
t.Union([t.String(), t.Number()])
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Enum Pattern
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
const Status = t.Union([
|
|
159
|
+
t.Literal('draft'),
|
|
160
|
+
t.Literal('published'),
|
|
161
|
+
t.Literal('archived')
|
|
162
|
+
])
|
|
163
|
+
|
|
164
|
+
app.post('/posts', ({ body }) => body, {
|
|
165
|
+
body: t.Object({
|
|
166
|
+
title: t.String(),
|
|
167
|
+
status: Status
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Complete Example
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
import { Elysia, t } from 'elysia'
|
|
176
|
+
|
|
177
|
+
const CreateUserSchema = t.Object({
|
|
178
|
+
name: t.String({ minLength: 1, maxLength: 100 }),
|
|
179
|
+
email: t.String({ format: 'email' }),
|
|
180
|
+
age: t.Optional(t.Number({ minimum: 0 })),
|
|
181
|
+
role: t.Union([
|
|
182
|
+
t.Literal('user'),
|
|
183
|
+
t.Literal('admin')
|
|
184
|
+
]),
|
|
185
|
+
tags: t.Optional(t.Array(t.String()))
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const app = new Elysia()
|
|
189
|
+
.post('/users', ({ body }) => {
|
|
190
|
+
return createUser(body)
|
|
191
|
+
}, {
|
|
192
|
+
body: CreateUserSchema
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Custom Error Messages
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
t.String({
|
|
200
|
+
minLength: 1,
|
|
201
|
+
error: 'Name is required'
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
t.Number({
|
|
205
|
+
minimum: 0,
|
|
206
|
+
error: 'Age must be positive'
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Reusable Models
|
|
211
|
+
|
|
212
|
+
Register schemas for reuse:
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
const app = new Elysia()
|
|
216
|
+
.model({
|
|
217
|
+
user: t.Object({
|
|
218
|
+
name: t.String(),
|
|
219
|
+
email: t.String({ format: 'email' })
|
|
220
|
+
}),
|
|
221
|
+
pagination: t.Object({
|
|
222
|
+
page: t.Numeric({ default: 1 }),
|
|
223
|
+
limit: t.Numeric({ default: 10 })
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
.post('/users', ({ body }) => body, {
|
|
227
|
+
body: 'user' // Reference by name
|
|
228
|
+
})
|
|
229
|
+
.get('/users', ({ query }) => query, {
|
|
230
|
+
query: 'pagination'
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Validation Error Handling
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const app = new Elysia()
|
|
238
|
+
.onError(({ code, error, set }) => {
|
|
239
|
+
if (code === 'VALIDATION') {
|
|
240
|
+
set.status = 422
|
|
241
|
+
return {
|
|
242
|
+
error: 'Validation failed',
|
|
243
|
+
details: error.all // Array of all errors
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
```
|