@smicolon/ai-kit 0.3.2 → 0.4.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 +73 -40
- package/dist/index.js +260 -126
- package/package.json +5 -5
- package/.claude-plugin/marketplace.json +0 -369
- package/packs/architect/CHANGELOG.md +0 -17
- package/packs/architect/README.md +0 -58
- package/packs/architect/agents/system-architect.md +0 -768
- package/packs/architect/commands/diagram-create.md +0 -300
- package/packs/better-auth/.mcp.json +0 -14
- package/packs/better-auth/CHANGELOG.md +0 -26
- package/packs/better-auth/README.md +0 -125
- package/packs/better-auth/agents/auth-architect.md +0 -278
- package/packs/better-auth/commands/auth-provider-add.md +0 -265
- package/packs/better-auth/commands/auth-setup.md +0 -298
- package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
- package/packs/dev-loop/CHANGELOG.md +0 -69
- package/packs/dev-loop/README.md +0 -155
- package/packs/dev-loop/commands/cancel-dev.md +0 -21
- package/packs/dev-loop/commands/dev-loop.md +0 -72
- package/packs/dev-loop/commands/dev-plan.md +0 -351
- package/packs/dev-loop/hooks/hooks.json +0 -15
- package/packs/dev-loop/hooks/stop-hook.sh +0 -178
- package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
- package/packs/django/CHANGELOG.md +0 -39
- package/packs/django/README.md +0 -92
- package/packs/django/agents/django-architect.md +0 -182
- package/packs/django/agents/django-builder.md +0 -250
- package/packs/django/agents/django-feature-based.md +0 -420
- package/packs/django/agents/django-reviewer.md +0 -253
- package/packs/django/agents/django-tester.md +0 -230
- package/packs/django/commands/api-endpoint.md +0 -285
- package/packs/django/commands/model-create.md +0 -178
- package/packs/django/commands/test-generate.md +0 -325
- package/packs/django/rules/migrations.md +0 -138
- package/packs/django/rules/models.md +0 -167
- package/packs/django/rules/serializers.md +0 -126
- package/packs/django/rules/services.md +0 -131
- package/packs/django/rules/tests.md +0 -140
- package/packs/django/rules/views.md +0 -102
- package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
- package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
- package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
- package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
- package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
- package/packs/django/skills/security-first-validator/SKILL.md +0 -435
- package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
- package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
- package/packs/failure-log/CHANGELOG.md +0 -20
- package/packs/failure-log/README.md +0 -168
- package/packs/failure-log/commands/failure-add.md +0 -106
- package/packs/failure-log/commands/failure-list.md +0 -89
- package/packs/failure-log/hooks/hooks.json +0 -16
- package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
- package/packs/flutter/CHANGELOG.md +0 -19
- package/packs/flutter/README.md +0 -170
- package/packs/flutter/agents/flutter-architect.md +0 -166
- package/packs/flutter/agents/flutter-builder.md +0 -303
- package/packs/flutter/agents/release-manager.md +0 -355
- package/packs/flutter/commands/fastlane-setup.md +0 -188
- package/packs/flutter/commands/flutter-build.md +0 -90
- package/packs/flutter/commands/flutter-deploy.md +0 -133
- package/packs/flutter/commands/flutter-test.md +0 -117
- package/packs/flutter/commands/signing-setup.md +0 -209
- package/packs/flutter/hooks/hooks.json +0 -17
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
- package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
- package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
- package/packs/hono/CHANGELOG.md +0 -19
- package/packs/hono/README.md +0 -143
- package/packs/hono/agents/hono-architect.md +0 -240
- package/packs/hono/agents/hono-builder.md +0 -285
- package/packs/hono/agents/hono-reviewer.md +0 -279
- package/packs/hono/agents/hono-tester.md +0 -346
- package/packs/hono/commands/middleware-create.md +0 -223
- package/packs/hono/commands/project-init.md +0 -306
- package/packs/hono/commands/route-create.md +0 -153
- package/packs/hono/commands/rpc-client.md +0 -263
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
- package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
- package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
- package/packs/hono/skills/zod-validation/SKILL.md +0 -332
- package/packs/nestjs/CHANGELOG.md +0 -29
- package/packs/nestjs/README.md +0 -75
- package/packs/nestjs/agents/nestjs-architect.md +0 -402
- package/packs/nestjs/agents/nestjs-builder.md +0 -301
- package/packs/nestjs/agents/nestjs-tester.md +0 -437
- package/packs/nestjs/commands/module-create.md +0 -369
- package/packs/nestjs/rules/controllers.md +0 -92
- package/packs/nestjs/rules/dto.md +0 -124
- package/packs/nestjs/rules/entities.md +0 -102
- package/packs/nestjs/rules/services.md +0 -106
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
- package/packs/nextjs/CHANGELOG.md +0 -36
- package/packs/nextjs/README.md +0 -76
- package/packs/nextjs/agents/frontend-tester.md +0 -680
- package/packs/nextjs/agents/frontend-visual.md +0 -820
- package/packs/nextjs/agents/nextjs-architect.md +0 -331
- package/packs/nextjs/agents/nextjs-modular.md +0 -433
- package/packs/nextjs/commands/component-create.md +0 -398
- package/packs/nextjs/rules/api-routes.md +0 -129
- package/packs/nextjs/rules/components.md +0 -106
- package/packs/nextjs/rules/hooks.md +0 -132
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
- package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
- package/packs/nuxtjs/CHANGELOG.md +0 -30
- package/packs/nuxtjs/README.md +0 -56
- package/packs/nuxtjs/agents/frontend-tester.md +0 -680
- package/packs/nuxtjs/agents/frontend-visual.md +0 -820
- package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
- package/packs/nuxtjs/commands/component-create.md +0 -223
- package/packs/nuxtjs/rules/components.md +0 -101
- package/packs/nuxtjs/rules/composables.md +0 -118
- package/packs/nuxtjs/rules/server-routes.md +0 -127
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
- package/packs/onboard/CHANGELOG.md +0 -22
- package/packs/onboard/README.md +0 -103
- package/packs/onboard/agents/onboard-guide.md +0 -118
- package/packs/onboard/commands/onboard.md +0 -313
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
- package/packs/tanstack-router/CHANGELOG.md +0 -30
- package/packs/tanstack-router/README.md +0 -113
- package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
- package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
- package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
- package/packs/tanstack-router/commands/form-create.md +0 -313
- package/packs/tanstack-router/commands/query-create.md +0 -263
- package/packs/tanstack-router/commands/route-create.md +0 -190
- package/packs/tanstack-router/commands/table-create.md +0 -413
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
- package/packs/worktree/CHANGELOG.md +0 -45
- package/packs/worktree/README.md +0 -219
- package/packs/worktree/commands/wt.md +0 -93
- package/packs/worktree/scripts/wt.sh +0 -957
- package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rpc-typesafe
|
|
3
|
-
description: This skill activates when setting up Hono RPC client, configuring type-safe API calls, or discussing client-server type sharing. It provides patterns for the hc client, type inference, and React Query integration.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Hono RPC Type Safety
|
|
7
|
-
|
|
8
|
-
Patterns for type-safe client-server communication with Hono.
|
|
9
|
-
|
|
10
|
-
## Server Setup
|
|
11
|
-
|
|
12
|
-
### Export App Type
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
// src/index.ts
|
|
16
|
-
import { Hono } from 'hono'
|
|
17
|
-
import { zValidator } from '@hono/zod-validator'
|
|
18
|
-
import { z } from 'zod'
|
|
19
|
-
|
|
20
|
-
const app = new Hono()
|
|
21
|
-
|
|
22
|
-
// Define routes with validators for full type inference
|
|
23
|
-
const routes = app
|
|
24
|
-
.get('/users', async (c) => {
|
|
25
|
-
return c.json({ users: [{ id: '1', name: 'John' }] })
|
|
26
|
-
})
|
|
27
|
-
.post('/users',
|
|
28
|
-
zValidator('json', z.object({
|
|
29
|
-
email: z.string().email(),
|
|
30
|
-
name: z.string()
|
|
31
|
-
})),
|
|
32
|
-
async (c) => {
|
|
33
|
-
const data = c.req.valid('json')
|
|
34
|
-
return c.json({ id: crypto.randomUUID(), ...data }, 201)
|
|
35
|
-
}
|
|
36
|
-
)
|
|
37
|
-
.get('/users/:id', async (c) => {
|
|
38
|
-
const id = c.req.param('id')
|
|
39
|
-
return c.json({ id, name: 'John' })
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
export default routes
|
|
43
|
-
|
|
44
|
-
// CRITICAL: Export type for client
|
|
45
|
-
export type AppType = typeof routes
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Route Chaining for Type Inference
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
// Chain routes to preserve types
|
|
52
|
-
const userRoutes = new Hono()
|
|
53
|
-
.get('/', async (c) => c.json({ users: [] }))
|
|
54
|
-
.post('/',
|
|
55
|
-
zValidator('json', createUserSchema),
|
|
56
|
-
async (c) => c.json({ id: '1' }, 201)
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
const postRoutes = new Hono()
|
|
60
|
-
.get('/', async (c) => c.json({ posts: [] }))
|
|
61
|
-
.post('/',
|
|
62
|
-
zValidator('json', createPostSchema),
|
|
63
|
-
async (c) => c.json({ id: '1' }, 201)
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
// Compose and export
|
|
67
|
-
const app = new Hono()
|
|
68
|
-
.route('/users', userRoutes)
|
|
69
|
-
.route('/posts', postRoutes)
|
|
70
|
-
|
|
71
|
-
export type AppType = typeof app
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Client Setup
|
|
75
|
-
|
|
76
|
-
### Basic Client
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
// client.ts
|
|
80
|
-
import { hc } from 'hono/client'
|
|
81
|
-
import type { AppType } from './server'
|
|
82
|
-
|
|
83
|
-
// Create typed client
|
|
84
|
-
const client = hc<AppType>('http://localhost:8787')
|
|
85
|
-
|
|
86
|
-
// Type-safe requests
|
|
87
|
-
const res = await client.users.$get()
|
|
88
|
-
const data = await res.json() // Typed!
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Client Factory
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// lib/api-client.ts
|
|
95
|
-
import { hc } from 'hono/client'
|
|
96
|
-
import type { AppType } from '@/server'
|
|
97
|
-
|
|
98
|
-
export function createClient(baseUrl: string, token?: string) {
|
|
99
|
-
return hc<AppType>(baseUrl, {
|
|
100
|
-
headers: token ? { Authorization: `Bearer ${token}` } : undefined
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Default instance
|
|
105
|
-
export const api = createClient(
|
|
106
|
-
process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8787'
|
|
107
|
-
)
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Request Patterns
|
|
111
|
-
|
|
112
|
-
### GET Requests
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
// Simple GET
|
|
116
|
-
const res = await api.users.$get()
|
|
117
|
-
const { users } = await res.json()
|
|
118
|
-
|
|
119
|
-
// GET with query params
|
|
120
|
-
const res = await api.users.$get({
|
|
121
|
-
query: {
|
|
122
|
-
page: 1,
|
|
123
|
-
limit: 20,
|
|
124
|
-
search: 'john'
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// GET with path params
|
|
129
|
-
const res = await api.users[':id'].$get({
|
|
130
|
-
param: { id: 'user-123' }
|
|
131
|
-
})
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### POST Requests
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
// POST with JSON body
|
|
138
|
-
const res = await api.users.$post({
|
|
139
|
-
json: {
|
|
140
|
-
email: 'user@example.com',
|
|
141
|
-
name: 'New User'
|
|
142
|
-
}
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
if (res.ok) {
|
|
146
|
-
const user = await res.json()
|
|
147
|
-
console.log(user.id) // Typed!
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### PUT/PATCH Requests
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
const res = await api.users[':id'].$put({
|
|
155
|
-
param: { id: 'user-123' },
|
|
156
|
-
json: {
|
|
157
|
-
name: 'Updated Name'
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### DELETE Requests
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
const res = await api.users[':id'].$delete({
|
|
166
|
-
param: { id: 'user-123' }
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
if (res.status === 204) {
|
|
170
|
-
console.log('Deleted successfully')
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## Type Utilities
|
|
175
|
-
|
|
176
|
-
### InferRequestType / InferResponseType
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
import { hc, InferRequestType, InferResponseType } from 'hono/client'
|
|
180
|
-
import type { AppType } from './server'
|
|
181
|
-
|
|
182
|
-
// Get specific endpoint type
|
|
183
|
-
type CreateUserRequest = InferRequestType<typeof api.users.$post>['json']
|
|
184
|
-
// { email: string; name: string }
|
|
185
|
-
|
|
186
|
-
type UserResponse = InferResponseType<typeof api.users[':id'].$get>
|
|
187
|
-
// { id: string; name: string }
|
|
188
|
-
|
|
189
|
-
// Use in functions
|
|
190
|
-
async function createUser(data: CreateUserRequest): Promise<UserResponse> {
|
|
191
|
-
const res = await api.users.$post({ json: data })
|
|
192
|
-
return res.json()
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### URL Generation
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
// Generate URL without making request
|
|
200
|
-
const url = api.users[':id'].$url({
|
|
201
|
-
param: { id: 'user-123' }
|
|
202
|
-
})
|
|
203
|
-
// 'http://localhost:8787/users/user-123'
|
|
204
|
-
|
|
205
|
-
// Useful for links, prefetching, etc.
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## Error Handling
|
|
209
|
-
|
|
210
|
-
### Response Status Checking
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
async function getUser(id: string) {
|
|
214
|
-
const res = await api.users[':id'].$get({ param: { id } })
|
|
215
|
-
|
|
216
|
-
if (!res.ok) {
|
|
217
|
-
if (res.status === 404) {
|
|
218
|
-
throw new Error('User not found')
|
|
219
|
-
}
|
|
220
|
-
throw new Error(`API error: ${res.status}`)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return res.json()
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Typed Error Responses
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
// Server defines error format
|
|
231
|
-
app.get('/users/:id', async (c) => {
|
|
232
|
-
const user = await getUser(c.req.param('id'))
|
|
233
|
-
|
|
234
|
-
if (!user) {
|
|
235
|
-
return c.json({ error: 'User not found' }, 404)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return c.json(user)
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
// Client handles typed errors
|
|
242
|
-
const res = await api.users[':id'].$get({ param: { id } })
|
|
243
|
-
|
|
244
|
-
if (res.status === 404) {
|
|
245
|
-
const { error } = await res.json()
|
|
246
|
-
console.log(error) // 'User not found'
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
## React Query Integration
|
|
251
|
-
|
|
252
|
-
### Query Hooks
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
256
|
-
import { api } from '@/lib/api-client'
|
|
257
|
-
|
|
258
|
-
// List query
|
|
259
|
-
export function useUsers(page = 1) {
|
|
260
|
-
return useQuery({
|
|
261
|
-
queryKey: ['users', page],
|
|
262
|
-
queryFn: async () => {
|
|
263
|
-
const res = await api.users.$get({ query: { page } })
|
|
264
|
-
if (!res.ok) throw new Error('Failed to fetch users')
|
|
265
|
-
return res.json()
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Single item query
|
|
271
|
-
export function useUser(id: string) {
|
|
272
|
-
return useQuery({
|
|
273
|
-
queryKey: ['users', id],
|
|
274
|
-
queryFn: async () => {
|
|
275
|
-
const res = await api.users[':id'].$get({ param: { id } })
|
|
276
|
-
if (!res.ok) throw new Error('User not found')
|
|
277
|
-
return res.json()
|
|
278
|
-
},
|
|
279
|
-
enabled: !!id
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Mutation Hooks
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
export function useCreateUser() {
|
|
288
|
-
const queryClient = useQueryClient()
|
|
289
|
-
|
|
290
|
-
return useMutation({
|
|
291
|
-
mutationFn: async (data: { email: string; name: string }) => {
|
|
292
|
-
const res = await api.users.$post({ json: data })
|
|
293
|
-
if (!res.ok) throw new Error('Failed to create user')
|
|
294
|
-
return res.json()
|
|
295
|
-
},
|
|
296
|
-
onSuccess: () => {
|
|
297
|
-
queryClient.invalidateQueries({ queryKey: ['users'] })
|
|
298
|
-
}
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export function useUpdateUser() {
|
|
303
|
-
const queryClient = useQueryClient()
|
|
304
|
-
|
|
305
|
-
return useMutation({
|
|
306
|
-
mutationFn: async ({ id, data }: { id: string; data: { name: string } }) => {
|
|
307
|
-
const res = await api.users[':id'].$put({ param: { id }, json: data })
|
|
308
|
-
if (!res.ok) throw new Error('Failed to update user')
|
|
309
|
-
return res.json()
|
|
310
|
-
},
|
|
311
|
-
onSuccess: (_, { id }) => {
|
|
312
|
-
queryClient.invalidateQueries({ queryKey: ['users', id] })
|
|
313
|
-
}
|
|
314
|
-
})
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### Component Usage
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
function UserList() {
|
|
322
|
-
const { data, isLoading, error } = useUsers()
|
|
323
|
-
const createUser = useCreateUser()
|
|
324
|
-
|
|
325
|
-
if (isLoading) return <div>Loading...</div>
|
|
326
|
-
if (error) return <div>Error: {error.message}</div>
|
|
327
|
-
|
|
328
|
-
return (
|
|
329
|
-
<div>
|
|
330
|
-
{data?.users.map(user => (
|
|
331
|
-
<div key={user.id}>{user.name}</div>
|
|
332
|
-
))}
|
|
333
|
-
|
|
334
|
-
<button
|
|
335
|
-
onClick={() => createUser.mutate({
|
|
336
|
-
email: 'new@example.com',
|
|
337
|
-
name: 'New User'
|
|
338
|
-
})}
|
|
339
|
-
>
|
|
340
|
-
Add User
|
|
341
|
-
</button>
|
|
342
|
-
</div>
|
|
343
|
-
)
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
## Configuration Requirements
|
|
348
|
-
|
|
349
|
-
### TypeScript Config
|
|
350
|
-
|
|
351
|
-
Both server and client need strict mode:
|
|
352
|
-
|
|
353
|
-
```json
|
|
354
|
-
{
|
|
355
|
-
"compilerOptions": {
|
|
356
|
-
"strict": true,
|
|
357
|
-
"moduleResolution": "bundler"
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### Status Codes for Type Discrimination
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
// Server: Explicit status codes enable type inference
|
|
366
|
-
app.post('/users', async (c) => {
|
|
367
|
-
return c.json({ id: '1', name: 'User' }, 201) // 201 for created
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
app.get('/users/:id', async (c) => {
|
|
371
|
-
const user = await getUser(id)
|
|
372
|
-
if (!user) {
|
|
373
|
-
return c.json({ error: 'Not found' }, 404) // 404 for not found
|
|
374
|
-
}
|
|
375
|
-
return c.json(user, 200) // 200 for success
|
|
376
|
-
})
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
## Best Practices
|
|
380
|
-
|
|
381
|
-
1. **Always export `AppType`** from server
|
|
382
|
-
2. **Chain routes** for proper type inference
|
|
383
|
-
3. **Use `zValidator`** for automatic request type inference
|
|
384
|
-
4. **Specify status codes** for response type discrimination
|
|
385
|
-
5. **Use `strict: true`** in both server and client tsconfig
|
|
386
|
-
6. **Create typed error handlers** for consistent error responses
|
|
387
|
-
7. **Wrap in React Query** for caching and state management
|
|
388
|
-
8. **Use `InferRequestType`/`InferResponseType`** for complex types
|
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: zod-validation
|
|
3
|
-
description: This skill activates when writing form validation, request validation, or Zod schemas in Hono. It provides patterns for validating JSON bodies, query parameters, path parameters, and headers with proper error handling.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Zod Validation in Hono
|
|
7
|
-
|
|
8
|
-
Patterns for request validation using Zod and @hono/zod-validator.
|
|
9
|
-
|
|
10
|
-
## Setup
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
bun add zod @hono/zod-validator
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Basic Validation
|
|
17
|
-
|
|
18
|
-
### JSON Body
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
import { zValidator } from '@hono/zod-validator'
|
|
22
|
-
import { z } from 'zod'
|
|
23
|
-
|
|
24
|
-
const createUserSchema = z.object({
|
|
25
|
-
email: z.string().email('Invalid email address'),
|
|
26
|
-
name: z.string().min(1, 'Name is required').max(100),
|
|
27
|
-
age: z.number().int().positive().optional(),
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
app.post('/users',
|
|
31
|
-
zValidator('json', createUserSchema),
|
|
32
|
-
async (c) => {
|
|
33
|
-
const data = c.req.valid('json')
|
|
34
|
-
// data is typed as { email: string; name: string; age?: number }
|
|
35
|
-
return c.json(data, 201)
|
|
36
|
-
}
|
|
37
|
-
)
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Query Parameters
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
const paginationSchema = z.object({
|
|
44
|
-
page: z.coerce.number().int().positive().default(1),
|
|
45
|
-
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
46
|
-
sort: z.enum(['asc', 'desc']).default('desc'),
|
|
47
|
-
search: z.string().optional(),
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
app.get('/users',
|
|
51
|
-
zValidator('query', paginationSchema),
|
|
52
|
-
async (c) => {
|
|
53
|
-
const { page, limit, sort, search } = c.req.valid('query')
|
|
54
|
-
// All values are properly typed and coerced
|
|
55
|
-
return c.json({ page, limit, sort, search })
|
|
56
|
-
}
|
|
57
|
-
)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Path Parameters
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
const userParamsSchema = z.object({
|
|
64
|
-
id: z.string().uuid('Invalid user ID format'),
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
app.get('/users/:id',
|
|
68
|
-
zValidator('param', userParamsSchema),
|
|
69
|
-
async (c) => {
|
|
70
|
-
const { id } = c.req.valid('param')
|
|
71
|
-
return c.json({ id })
|
|
72
|
-
}
|
|
73
|
-
)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Headers
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
const authHeaderSchema = z.object({
|
|
80
|
-
authorization: z.string().startsWith('Bearer '),
|
|
81
|
-
'x-request-id': z.string().uuid().optional(),
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
app.get('/protected',
|
|
85
|
-
zValidator('header', authHeaderSchema),
|
|
86
|
-
async (c) => {
|
|
87
|
-
const headers = c.req.valid('header')
|
|
88
|
-
return c.json({ authenticated: true })
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Form Data
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
const uploadSchema = z.object({
|
|
97
|
-
title: z.string().min(1),
|
|
98
|
-
description: z.string().optional(),
|
|
99
|
-
// File validation happens separately
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
app.post('/upload',
|
|
103
|
-
zValidator('form', uploadSchema),
|
|
104
|
-
async (c) => {
|
|
105
|
-
const { title, description } = c.req.valid('form')
|
|
106
|
-
return c.json({ title, description })
|
|
107
|
-
}
|
|
108
|
-
)
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Schema Patterns
|
|
112
|
-
|
|
113
|
-
### Reusable Field Schemas
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
// validators/common.ts
|
|
117
|
-
export const emailSchema = z.string().email('Invalid email')
|
|
118
|
-
export const uuidSchema = z.string().uuid('Invalid ID format')
|
|
119
|
-
export const dateSchema = z.coerce.date()
|
|
120
|
-
|
|
121
|
-
export const paginationSchema = z.object({
|
|
122
|
-
page: z.coerce.number().int().positive().default(1),
|
|
123
|
-
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
124
|
-
})
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Create/Update Pattern
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// validators/user.schema.ts
|
|
131
|
-
export const createUserSchema = z.object({
|
|
132
|
-
email: z.string().email(),
|
|
133
|
-
name: z.string().min(1).max(100),
|
|
134
|
-
password: z.string().min(8),
|
|
135
|
-
role: z.enum(['user', 'admin']).default('user'),
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
// Partial for updates (all fields optional)
|
|
139
|
-
export const updateUserSchema = createUserSchema.partial()
|
|
140
|
-
|
|
141
|
-
// Omit for specific updates
|
|
142
|
-
export const updatePasswordSchema = createUserSchema.pick({
|
|
143
|
-
password: true,
|
|
144
|
-
}).extend({
|
|
145
|
-
currentPassword: z.string(),
|
|
146
|
-
confirmPassword: z.string(),
|
|
147
|
-
}).refine(data => data.password === data.confirmPassword, {
|
|
148
|
-
message: 'Passwords do not match',
|
|
149
|
-
path: ['confirmPassword'],
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
// Infer TypeScript types
|
|
153
|
-
export type CreateUser = z.infer<typeof createUserSchema>
|
|
154
|
-
export type UpdateUser = z.infer<typeof updateUserSchema>
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Nested Objects
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
const addressSchema = z.object({
|
|
161
|
-
street: z.string(),
|
|
162
|
-
city: z.string(),
|
|
163
|
-
country: z.string(),
|
|
164
|
-
zip: z.string(),
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const orderSchema = z.object({
|
|
168
|
-
items: z.array(z.object({
|
|
169
|
-
productId: z.string().uuid(),
|
|
170
|
-
quantity: z.number().int().positive(),
|
|
171
|
-
})).min(1, 'At least one item required'),
|
|
172
|
-
shippingAddress: addressSchema,
|
|
173
|
-
billingAddress: addressSchema.optional(),
|
|
174
|
-
})
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Conditional Validation
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
const paymentSchema = z.discriminatedUnion('method', [
|
|
181
|
-
z.object({
|
|
182
|
-
method: z.literal('card'),
|
|
183
|
-
cardNumber: z.string().length(16),
|
|
184
|
-
cvv: z.string().length(3),
|
|
185
|
-
}),
|
|
186
|
-
z.object({
|
|
187
|
-
method: z.literal('paypal'),
|
|
188
|
-
paypalEmail: z.string().email(),
|
|
189
|
-
}),
|
|
190
|
-
z.object({
|
|
191
|
-
method: z.literal('bank'),
|
|
192
|
-
accountNumber: z.string(),
|
|
193
|
-
routingNumber: z.string(),
|
|
194
|
-
}),
|
|
195
|
-
])
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Custom Refinements
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
const registrationSchema = z.object({
|
|
202
|
-
password: z.string().min(8),
|
|
203
|
-
confirmPassword: z.string(),
|
|
204
|
-
}).refine(data => data.password === data.confirmPassword, {
|
|
205
|
-
message: 'Passwords must match',
|
|
206
|
-
path: ['confirmPassword'],
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
const dateRangeSchema = z.object({
|
|
210
|
-
startDate: z.coerce.date(),
|
|
211
|
-
endDate: z.coerce.date(),
|
|
212
|
-
}).refine(data => data.endDate > data.startDate, {
|
|
213
|
-
message: 'End date must be after start date',
|
|
214
|
-
path: ['endDate'],
|
|
215
|
-
})
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Transform
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
const userInputSchema = z.object({
|
|
222
|
-
email: z.string().email().toLowerCase().trim(),
|
|
223
|
-
name: z.string().trim(),
|
|
224
|
-
tags: z.string().transform(s => s.split(',').map(t => t.trim())),
|
|
225
|
-
})
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Custom Error Handling
|
|
229
|
-
|
|
230
|
-
### Custom Error Hook
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
import { zValidator } from '@hono/zod-validator'
|
|
234
|
-
|
|
235
|
-
const customValidator = <T extends z.ZodType>(
|
|
236
|
-
target: 'json' | 'query' | 'param' | 'header' | 'form',
|
|
237
|
-
schema: T
|
|
238
|
-
) => {
|
|
239
|
-
return zValidator(target, schema, (result, c) => {
|
|
240
|
-
if (!result.success) {
|
|
241
|
-
const errors = result.error.issues.map(issue => ({
|
|
242
|
-
field: issue.path.join('.'),
|
|
243
|
-
message: issue.message,
|
|
244
|
-
}))
|
|
245
|
-
|
|
246
|
-
return c.json({
|
|
247
|
-
error: 'Validation failed',
|
|
248
|
-
details: errors,
|
|
249
|
-
}, 400)
|
|
250
|
-
}
|
|
251
|
-
})
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Usage
|
|
255
|
-
app.post('/users',
|
|
256
|
-
customValidator('json', createUserSchema),
|
|
257
|
-
async (c) => {
|
|
258
|
-
const data = c.req.valid('json')
|
|
259
|
-
return c.json(data)
|
|
260
|
-
}
|
|
261
|
-
)
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Validation Error Response Format
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
// Standard error format
|
|
268
|
-
{
|
|
269
|
-
"error": "Validation failed",
|
|
270
|
-
"details": [
|
|
271
|
-
{ "field": "email", "message": "Invalid email address" },
|
|
272
|
-
{ "field": "name", "message": "Name is required" }
|
|
273
|
-
]
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## Multiple Validators
|
|
278
|
-
|
|
279
|
-
Chain validators for different request parts:
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
app.put('/users/:id',
|
|
283
|
-
zValidator('param', userParamsSchema),
|
|
284
|
-
zValidator('json', updateUserSchema),
|
|
285
|
-
async (c) => {
|
|
286
|
-
const { id } = c.req.valid('param')
|
|
287
|
-
const data = c.req.valid('json')
|
|
288
|
-
|
|
289
|
-
return c.json({ id, ...data })
|
|
290
|
-
}
|
|
291
|
-
)
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## Type Export Pattern
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
// validators/user.schema.ts
|
|
298
|
-
import { z } from 'zod'
|
|
299
|
-
|
|
300
|
-
export const createUserSchema = z.object({
|
|
301
|
-
email: z.string().email(),
|
|
302
|
-
name: z.string(),
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
export const updateUserSchema = createUserSchema.partial()
|
|
306
|
-
|
|
307
|
-
export const userParamsSchema = z.object({
|
|
308
|
-
id: z.string().uuid(),
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
export const userQuerySchema = z.object({
|
|
312
|
-
page: z.coerce.number().default(1),
|
|
313
|
-
limit: z.coerce.number().default(20),
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
// Export inferred types
|
|
317
|
-
export type CreateUser = z.infer<typeof createUserSchema>
|
|
318
|
-
export type UpdateUser = z.infer<typeof updateUserSchema>
|
|
319
|
-
export type UserParams = z.infer<typeof userParamsSchema>
|
|
320
|
-
export type UserQuery = z.infer<typeof userQuerySchema>
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
## Best Practices
|
|
324
|
-
|
|
325
|
-
1. **Always use `zValidator`** for all request inputs
|
|
326
|
-
2. **Use `z.coerce`** for query params (they're always strings)
|
|
327
|
-
3. **Provide clear error messages** in schema definitions
|
|
328
|
-
4. **Export inferred types** for use elsewhere
|
|
329
|
-
5. **Create reusable field schemas** for common patterns
|
|
330
|
-
6. **Use `.partial()`** for update schemas
|
|
331
|
-
7. **Use `.refine()`** for cross-field validation
|
|
332
|
-
8. **Set sensible defaults** with `.default()`
|