start-vibing 2.0.11 → 2.0.13
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 +177 -177
- package/dist/cli.js +19 -2
- package/package.json +42 -42
- package/template/.claude/CLAUDE.md +174 -174
- package/template/.claude/agents/01-orchestration/agent-selector.md +130 -130
- package/template/.claude/agents/01-orchestration/checkpoint-manager.md +142 -142
- package/template/.claude/agents/01-orchestration/context-manager.md +138 -138
- package/template/.claude/agents/01-orchestration/error-recovery.md +182 -182
- package/template/.claude/agents/01-orchestration/orchestrator.md +114 -114
- package/template/.claude/agents/01-orchestration/parallel-coordinator.md +141 -141
- package/template/.claude/agents/01-orchestration/task-decomposer.md +121 -121
- package/template/.claude/agents/01-orchestration/workflow-router.md +114 -114
- package/template/.claude/agents/02-typescript/bun-runtime-expert.md +197 -197
- package/template/.claude/agents/02-typescript/esm-resolver.md +193 -193
- package/template/.claude/agents/02-typescript/import-alias-enforcer.md +158 -158
- package/template/.claude/agents/02-typescript/ts-generics-helper.md +183 -183
- package/template/.claude/agents/02-typescript/ts-migration-helper.md +238 -238
- package/template/.claude/agents/02-typescript/ts-strict-checker.md +180 -180
- package/template/.claude/agents/02-typescript/ts-types-analyzer.md +199 -199
- package/template/.claude/agents/02-typescript/type-definition-writer.md +187 -187
- package/template/.claude/agents/02-typescript/zod-schema-designer.md +212 -212
- package/template/.claude/agents/02-typescript/zod-validator.md +158 -158
- package/template/.claude/agents/03-testing/playwright-assertions.md +265 -265
- package/template/.claude/agents/03-testing/playwright-e2e.md +247 -247
- package/template/.claude/agents/03-testing/playwright-fixtures.md +234 -234
- package/template/.claude/agents/03-testing/playwright-multi-viewport.md +256 -256
- package/template/.claude/agents/03-testing/playwright-page-objects.md +247 -247
- package/template/.claude/agents/03-testing/test-cleanup-manager.md +248 -248
- package/template/.claude/agents/03-testing/test-data-generator.md +254 -254
- package/template/.claude/agents/03-testing/tester-integration.md +278 -278
- package/template/.claude/agents/03-testing/tester-unit.md +207 -207
- package/template/.claude/agents/03-testing/vitest-config.md +287 -287
- package/template/.claude/agents/04-docker/container-health.md +255 -255
- package/template/.claude/agents/04-docker/deployment-validator.md +225 -225
- package/template/.claude/agents/04-docker/docker-compose-designer.md +281 -281
- package/template/.claude/agents/04-docker/docker-env-manager.md +235 -235
- package/template/.claude/agents/04-docker/docker-multi-stage.md +241 -241
- package/template/.claude/agents/04-docker/dockerfile-optimizer.md +208 -208
- package/template/.claude/agents/05-database/database-seeder.md +273 -273
- package/template/.claude/agents/05-database/mongodb-query-optimizer.md +230 -230
- package/template/.claude/agents/05-database/mongoose-aggregation.md +306 -306
- package/template/.claude/agents/05-database/mongoose-index-optimizer.md +182 -182
- package/template/.claude/agents/05-database/mongoose-schema-designer.md +267 -267
- package/template/.claude/agents/06-security/auth-session-validator.md +68 -68
- package/template/.claude/agents/06-security/input-sanitizer.md +80 -80
- package/template/.claude/agents/06-security/owasp-checker.md +97 -97
- package/template/.claude/agents/06-security/permission-auditor.md +100 -100
- package/template/.claude/agents/06-security/security-auditor.md +84 -84
- package/template/.claude/agents/06-security/sensitive-data-scanner.md +83 -83
- package/template/.claude/agents/07-documentation/api-documenter.md +136 -136
- package/template/.claude/agents/07-documentation/changelog-manager.md +105 -105
- package/template/.claude/agents/07-documentation/documenter.md +76 -76
- package/template/.claude/agents/07-documentation/domain-updater.md +81 -81
- package/template/.claude/agents/07-documentation/jsdoc-generator.md +114 -114
- package/template/.claude/agents/07-documentation/readme-generator.md +135 -135
- package/template/.claude/agents/08-git/branch-manager.md +58 -58
- package/template/.claude/agents/08-git/commit-manager.md +63 -63
- package/template/.claude/agents/08-git/pr-creator.md +76 -76
- package/template/.claude/agents/09-quality/code-reviewer.md +71 -71
- package/template/.claude/agents/09-quality/quality-checker.md +67 -67
- package/template/.claude/agents/10-research/best-practices-finder.md +89 -89
- package/template/.claude/agents/10-research/competitor-analyzer.md +106 -106
- package/template/.claude/agents/10-research/pattern-researcher.md +93 -93
- package/template/.claude/agents/10-research/research-cache-manager.md +76 -76
- package/template/.claude/agents/10-research/research-web.md +98 -98
- package/template/.claude/agents/10-research/tech-evaluator.md +101 -101
- package/template/.claude/agents/11-ui-ux/accessibility-auditor.md +136 -136
- package/template/.claude/agents/11-ui-ux/design-system-enforcer.md +125 -125
- package/template/.claude/agents/11-ui-ux/skeleton-generator.md +118 -118
- package/template/.claude/agents/11-ui-ux/ui-desktop.md +132 -132
- package/template/.claude/agents/11-ui-ux/ui-mobile.md +98 -98
- package/template/.claude/agents/11-ui-ux/ui-tablet.md +110 -110
- package/template/.claude/agents/12-performance/api-latency-analyzer.md +156 -156
- package/template/.claude/agents/12-performance/bundle-analyzer.md +113 -113
- package/template/.claude/agents/12-performance/memory-leak-detector.md +137 -137
- package/template/.claude/agents/12-performance/performance-profiler.md +115 -115
- package/template/.claude/agents/12-performance/query-optimizer.md +124 -124
- package/template/.claude/agents/12-performance/render-optimizer.md +154 -154
- package/template/.claude/agents/13-debugging/build-error-fixer.md +207 -207
- package/template/.claude/agents/13-debugging/debugger.md +149 -149
- package/template/.claude/agents/13-debugging/error-stack-analyzer.md +141 -141
- package/template/.claude/agents/13-debugging/network-debugger.md +208 -208
- package/template/.claude/agents/13-debugging/runtime-error-fixer.md +181 -181
- package/template/.claude/agents/13-debugging/type-error-resolver.md +185 -185
- package/template/.claude/agents/14-validation/final-validator.md +93 -93
- package/template/.claude/agents/_backup/analyzer.md +134 -134
- package/template/.claude/agents/_backup/code-reviewer.md +279 -279
- package/template/.claude/agents/_backup/commit-manager.md +219 -219
- package/template/.claude/agents/_backup/debugger.md +280 -280
- package/template/.claude/agents/_backup/documenter.md +237 -237
- package/template/.claude/agents/_backup/domain-updater.md +197 -197
- package/template/.claude/agents/_backup/final-validator.md +169 -169
- package/template/.claude/agents/_backup/orchestrator.md +149 -149
- package/template/.claude/agents/_backup/performance.md +232 -232
- package/template/.claude/agents/_backup/quality-checker.md +240 -240
- package/template/.claude/agents/_backup/research.md +315 -315
- package/template/.claude/agents/_backup/security-auditor.md +192 -192
- package/template/.claude/agents/_backup/tester.md +566 -566
- package/template/.claude/agents/_backup/ui-ux-reviewer.md +247 -247
- package/template/.claude/config/README.md +30 -30
- package/template/.claude/config/mcp-config.json +344 -344
- package/template/.claude/config/project-config.json +53 -53
- package/template/.claude/config/quality-gates.json +46 -46
- package/template/.claude/config/security-rules.json +45 -45
- package/template/.claude/config/testing-config.json +164 -164
- package/template/.claude/hooks/SETUP.md +126 -126
- package/template/.claude/hooks/run-hook.ts +176 -176
- package/template/.claude/hooks/stop-validator.ts +914 -824
- package/template/.claude/hooks/user-prompt-submit.ts +886 -886
- package/template/.claude/scripts/mcp-quick-install.ts +151 -151
- package/template/.claude/scripts/setup-mcps.ts +651 -651
- package/template/.claude/settings.json +275 -275
- package/template/.claude/skills/bun-runtime/SKILL.md +430 -430
- package/template/.claude/skills/codebase-knowledge/domains/claude-system.md +431 -431
- package/template/.claude/skills/codebase-knowledge/domains/mcp-integration.md +295 -295
- package/template/.claude/skills/debugging-patterns/SKILL.md +485 -485
- package/template/.claude/skills/docker-patterns/SKILL.md +555 -555
- package/template/.claude/skills/git-workflow/SKILL.md +454 -454
- package/template/.claude/skills/mongoose-patterns/SKILL.md +499 -499
- package/template/.claude/skills/nextjs-app-router/SKILL.md +327 -327
- package/template/.claude/skills/performance-patterns/SKILL.md +547 -547
- package/template/.claude/skills/playwright-automation/SKILL.md +438 -438
- package/template/.claude/skills/react-patterns/SKILL.md +389 -389
- package/template/.claude/skills/research-cache/SKILL.md +222 -222
- package/template/.claude/skills/shadcn-ui/SKILL.md +511 -511
- package/template/.claude/skills/tailwind-patterns/SKILL.md +465 -465
- package/template/.claude/skills/test-coverage/SKILL.md +467 -467
- package/template/.claude/skills/trpc-api/SKILL.md +434 -434
- package/template/.claude/skills/typescript-strict/SKILL.md +367 -367
- package/template/.claude/skills/zod-validation/SKILL.md +403 -403
- package/template/CLAUDE.md +117 -117
|
@@ -1,403 +1,403 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: zod-validation
|
|
3
|
-
description: Zod schema validation patterns. Input validation, type inference, transformations, error handling. Use when creating validation schemas or handling user input.
|
|
4
|
-
allowed-tools: Read, Write, Edit, Grep, Glob
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Zod Validation - Schema Design Patterns
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
Expert guidance for Zod validation:
|
|
12
|
-
|
|
13
|
-
- **Schema Design** - Create robust validation schemas
|
|
14
|
-
- **Type Inference** - Derive TypeScript types from schemas
|
|
15
|
-
- **Transformations** - Parse and transform data
|
|
16
|
-
- **Error Handling** - User-friendly error messages
|
|
17
|
-
- **Integration** - tRPC, forms, API routes
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Core Patterns
|
|
22
|
-
|
|
23
|
-
### Basic Schema
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
import { z } from 'zod';
|
|
27
|
-
|
|
28
|
-
// Define schema
|
|
29
|
-
const userSchema = z.object({
|
|
30
|
-
id: z.string().uuid(),
|
|
31
|
-
email: z.string().email(),
|
|
32
|
-
name: z.string().min(2).max(100),
|
|
33
|
-
age: z.number().int().positive().optional(),
|
|
34
|
-
role: z.enum(['admin', 'user', 'guest']),
|
|
35
|
-
createdAt: z.date(),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Infer TypeScript type
|
|
39
|
-
type User = z.infer<typeof userSchema>;
|
|
40
|
-
// { id: string; email: string; name: string; age?: number; role: 'admin' | 'user' | 'guest'; createdAt: Date }
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Input vs Output Types
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
const createUserSchema = z.object({
|
|
47
|
-
email: z.string().email(),
|
|
48
|
-
name: z.string().min(2),
|
|
49
|
-
password: z.string().min(8),
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Input type (what client sends)
|
|
53
|
-
type CreateUserInput = z.input<typeof createUserSchema>;
|
|
54
|
-
|
|
55
|
-
// Output type (after parsing)
|
|
56
|
-
type CreateUserOutput = z.output<typeof createUserSchema>;
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Common Validators
|
|
62
|
-
|
|
63
|
-
### Strings
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
z.string().email(); // Email format
|
|
67
|
-
z.string().url(); // URL format
|
|
68
|
-
z.string().uuid(); // UUID format
|
|
69
|
-
z.string().cuid(); // CUID format
|
|
70
|
-
z.string().min(1); // Non-empty
|
|
71
|
-
z.string().max(255); // Max length
|
|
72
|
-
z.string().regex(/^[a-z]+$/); // Custom regex
|
|
73
|
-
z.string().trim(); // Trim whitespace
|
|
74
|
-
z.string().toLowerCase(); // Transform to lowercase
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Numbers
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
z.number().int(); // Integer only
|
|
81
|
-
z.number().positive(); // > 0
|
|
82
|
-
z.number().nonnegative(); // >= 0
|
|
83
|
-
z.number().min(0).max(100); // Range
|
|
84
|
-
z.number().multipleOf(5); // Divisible by 5
|
|
85
|
-
z.number().finite(); // No Infinity
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Objects
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
// Required & optional
|
|
92
|
-
z.object({
|
|
93
|
-
required: z.string(),
|
|
94
|
-
optional: z.string().optional(),
|
|
95
|
-
nullable: z.string().nullable(),
|
|
96
|
-
default: z.string().default('value'),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Passthrough unknown keys
|
|
100
|
-
z.object({ name: z.string() }).passthrough();
|
|
101
|
-
|
|
102
|
-
// Strip unknown keys (default)
|
|
103
|
-
z.object({ name: z.string() }).strict();
|
|
104
|
-
|
|
105
|
-
// Extend schema
|
|
106
|
-
const baseSchema = z.object({ id: z.string() });
|
|
107
|
-
const extendedSchema = baseSchema.extend({ name: z.string() });
|
|
108
|
-
|
|
109
|
-
// Merge schemas
|
|
110
|
-
const merged = schemaA.merge(schemaB);
|
|
111
|
-
|
|
112
|
-
// Pick/omit fields
|
|
113
|
-
const partial = schema.pick({ name: true, email: true });
|
|
114
|
-
const omitted = schema.omit({ password: true });
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Arrays
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
z.array(z.string()); // String array
|
|
121
|
-
z.array(z.number()).min(1); // Non-empty array
|
|
122
|
-
z.array(z.object({})).max(10); // Max 10 items
|
|
123
|
-
z.string().array(); // Alternative syntax
|
|
124
|
-
z.tuple([z.string(), z.number()]); // Fixed tuple
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Transformations
|
|
130
|
-
|
|
131
|
-
### Transform
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
const slugSchema = z.string().transform((val) => val.toLowerCase().replace(/\s+/g, '-'));
|
|
135
|
-
|
|
136
|
-
slugSchema.parse('Hello World'); // 'hello-world'
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Coerce (Parse from string)
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// Useful for form data / URL params
|
|
143
|
-
z.coerce.number(); // "123" -> 123
|
|
144
|
-
z.coerce.boolean(); // "true" -> true
|
|
145
|
-
z.coerce.date(); // "2024-01-01" -> Date
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Preprocess
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
const trimmedString = z.preprocess(
|
|
152
|
-
(val) => (typeof val === 'string' ? val.trim() : val),
|
|
153
|
-
z.string().min(1)
|
|
154
|
-
);
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Refinements
|
|
160
|
-
|
|
161
|
-
### Simple Refinement
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
const passwordSchema = z
|
|
165
|
-
.string()
|
|
166
|
-
.min(8)
|
|
167
|
-
.refine((val) => /[A-Z]/.test(val), {
|
|
168
|
-
message: 'Must contain uppercase letter',
|
|
169
|
-
})
|
|
170
|
-
.refine((val) => /[0-9]/.test(val), {
|
|
171
|
-
message: 'Must contain number',
|
|
172
|
-
});
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### Super Refine (Multiple Errors)
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
const passwordSchema = z.string().superRefine((val, ctx) => {
|
|
179
|
-
if (val.length < 8) {
|
|
180
|
-
ctx.addIssue({
|
|
181
|
-
code: z.ZodIssueCode.too_small,
|
|
182
|
-
minimum: 8,
|
|
183
|
-
type: 'string',
|
|
184
|
-
inclusive: true,
|
|
185
|
-
message: 'Password must be at least 8 characters',
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
if (!/[A-Z]/.test(val)) {
|
|
189
|
-
ctx.addIssue({
|
|
190
|
-
code: z.ZodIssueCode.custom,
|
|
191
|
-
message: 'Must contain uppercase letter',
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Cross-field Validation
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
const registerSchema = z
|
|
201
|
-
.object({
|
|
202
|
-
password: z.string().min(8),
|
|
203
|
-
confirmPassword: z.string(),
|
|
204
|
-
})
|
|
205
|
-
.refine((data) => data.password === data.confirmPassword, {
|
|
206
|
-
message: "Passwords don't match",
|
|
207
|
-
path: ['confirmPassword'],
|
|
208
|
-
});
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## Error Handling
|
|
214
|
-
|
|
215
|
-
### Safe Parse
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
const result = userSchema.safeParse(input);
|
|
219
|
-
|
|
220
|
-
if (result.success) {
|
|
221
|
-
// result.data is typed as User
|
|
222
|
-
console.log(result.data);
|
|
223
|
-
} else {
|
|
224
|
-
// result.error is ZodError
|
|
225
|
-
console.log(result.error.issues);
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Custom Error Messages
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
const schema = z.object({
|
|
233
|
-
email: z
|
|
234
|
-
.string({
|
|
235
|
-
required_error: 'Email is required',
|
|
236
|
-
invalid_type_error: 'Email must be a string',
|
|
237
|
-
})
|
|
238
|
-
.email({ message: 'Invalid email format' }),
|
|
239
|
-
|
|
240
|
-
age: z
|
|
241
|
-
.number({
|
|
242
|
-
required_error: 'Age is required',
|
|
243
|
-
})
|
|
244
|
-
.min(18, { message: 'Must be 18 or older' }),
|
|
245
|
-
});
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Format Errors
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
import { z } from 'zod';
|
|
252
|
-
|
|
253
|
-
function formatZodErrors(error: z.ZodError): Record<string, string[]> {
|
|
254
|
-
const formatted: Record<string, string[]> = {};
|
|
255
|
-
|
|
256
|
-
for (const issue of error.issues) {
|
|
257
|
-
const path = issue.path.join('.');
|
|
258
|
-
if (!formatted[path]) {
|
|
259
|
-
formatted[path] = [];
|
|
260
|
-
}
|
|
261
|
-
formatted[path].push(issue.message);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return formatted;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Usage
|
|
268
|
-
const result = schema.safeParse(input);
|
|
269
|
-
if (!result.success) {
|
|
270
|
-
const errors = formatZodErrors(result.error);
|
|
271
|
-
// { email: ['Invalid email format'], password: ['Too short'] }
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## Integration Patterns
|
|
278
|
-
|
|
279
|
-
### tRPC Procedures
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
import { z } from 'zod';
|
|
283
|
-
import { publicProcedure, protectedProcedure } from '../trpc';
|
|
284
|
-
|
|
285
|
-
export const userRouter = {
|
|
286
|
-
getById: publicProcedure.input(z.object({ id: z.string().uuid() })).query(async ({ input }) => {
|
|
287
|
-
return db.user.findUnique({ where: { id: input.id } });
|
|
288
|
-
}),
|
|
289
|
-
|
|
290
|
-
create: protectedProcedure.input(createUserSchema).mutation(async ({ input, ctx }) => {
|
|
291
|
-
return db.user.create({ data: { ...input, createdBy: ctx.user.id } });
|
|
292
|
-
}),
|
|
293
|
-
};
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Server Actions (Next.js)
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
'use server';
|
|
300
|
-
|
|
301
|
-
import { z } from 'zod';
|
|
302
|
-
|
|
303
|
-
const formSchema = z.object({
|
|
304
|
-
name: z.string().min(2),
|
|
305
|
-
email: z.string().email(),
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
export async function submitForm(formData: FormData) {
|
|
309
|
-
const parsed = formSchema.safeParse({
|
|
310
|
-
name: formData.get('name'),
|
|
311
|
-
email: formData.get('email'),
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
if (!parsed.success) {
|
|
315
|
-
return { error: parsed.error.flatten() };
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Process valid data
|
|
319
|
-
await saveToDatabase(parsed.data);
|
|
320
|
-
return { success: true };
|
|
321
|
-
}
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### API Route Validation
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
import { z } from 'zod';
|
|
328
|
-
import { NextResponse } from 'next/server';
|
|
329
|
-
|
|
330
|
-
const bodySchema = z.object({
|
|
331
|
-
title: z.string().min(1),
|
|
332
|
-
content: z.string(),
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
export async function POST(request: Request) {
|
|
336
|
-
const body = await request.json();
|
|
337
|
-
const parsed = bodySchema.safeParse(body);
|
|
338
|
-
|
|
339
|
-
if (!parsed.success) {
|
|
340
|
-
return NextResponse.json({ errors: parsed.error.flatten() }, { status: 400 });
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Use parsed.data
|
|
344
|
-
const result = await createPost(parsed.data);
|
|
345
|
-
return NextResponse.json(result, { status: 201 });
|
|
346
|
-
}
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
## Schema Organization
|
|
352
|
-
|
|
353
|
-
### Structure
|
|
354
|
-
|
|
355
|
-
```
|
|
356
|
-
types/
|
|
357
|
-
├── schemas/
|
|
358
|
-
│ ├── user.schema.ts
|
|
359
|
-
│ ├── post.schema.ts
|
|
360
|
-
│ └── common.schema.ts
|
|
361
|
-
└── index.ts
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Reusable Schemas
|
|
365
|
-
|
|
366
|
-
```typescript
|
|
367
|
-
// types/schemas/common.schema.ts
|
|
368
|
-
export const idSchema = z.string().uuid();
|
|
369
|
-
export const emailSchema = z.string().email().toLowerCase();
|
|
370
|
-
export const timestampSchema = z.date().or(z.string().datetime());
|
|
371
|
-
|
|
372
|
-
export const paginationSchema = z.object({
|
|
373
|
-
page: z.coerce.number().int().positive().default(1),
|
|
374
|
-
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
375
|
-
});
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
## Agent Integration
|
|
381
|
-
|
|
382
|
-
This skill is used by:
|
|
383
|
-
|
|
384
|
-
- **zod-schema-designer** agent
|
|
385
|
-
- **zod-validator** agent
|
|
386
|
-
- **input-sanitizer** agent
|
|
387
|
-
- **security-auditor** for validation checks
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## FORBIDDEN
|
|
392
|
-
|
|
393
|
-
1. **`parse()` without try/catch** - Use `safeParse()` instead
|
|
394
|
-
2. **Missing validation on API inputs** - ALWAYS validate
|
|
395
|
-
3. **Inline schemas** - Define in types/ folder
|
|
396
|
-
4. **Generic error messages** - Be specific
|
|
397
|
-
5. **Skipping coercion for form data** - Use `z.coerce`
|
|
398
|
-
|
|
399
|
-
---
|
|
400
|
-
|
|
401
|
-
## Version
|
|
402
|
-
|
|
403
|
-
- **v1.0.0** - Initial implementation based on Zod 3.x patterns
|
|
1
|
+
---
|
|
2
|
+
name: zod-validation
|
|
3
|
+
description: Zod schema validation patterns. Input validation, type inference, transformations, error handling. Use when creating validation schemas or handling user input.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Zod Validation - Schema Design Patterns
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Expert guidance for Zod validation:
|
|
12
|
+
|
|
13
|
+
- **Schema Design** - Create robust validation schemas
|
|
14
|
+
- **Type Inference** - Derive TypeScript types from schemas
|
|
15
|
+
- **Transformations** - Parse and transform data
|
|
16
|
+
- **Error Handling** - User-friendly error messages
|
|
17
|
+
- **Integration** - tRPC, forms, API routes
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Core Patterns
|
|
22
|
+
|
|
23
|
+
### Basic Schema
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
|
|
28
|
+
// Define schema
|
|
29
|
+
const userSchema = z.object({
|
|
30
|
+
id: z.string().uuid(),
|
|
31
|
+
email: z.string().email(),
|
|
32
|
+
name: z.string().min(2).max(100),
|
|
33
|
+
age: z.number().int().positive().optional(),
|
|
34
|
+
role: z.enum(['admin', 'user', 'guest']),
|
|
35
|
+
createdAt: z.date(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Infer TypeScript type
|
|
39
|
+
type User = z.infer<typeof userSchema>;
|
|
40
|
+
// { id: string; email: string; name: string; age?: number; role: 'admin' | 'user' | 'guest'; createdAt: Date }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Input vs Output Types
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const createUserSchema = z.object({
|
|
47
|
+
email: z.string().email(),
|
|
48
|
+
name: z.string().min(2),
|
|
49
|
+
password: z.string().min(8),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Input type (what client sends)
|
|
53
|
+
type CreateUserInput = z.input<typeof createUserSchema>;
|
|
54
|
+
|
|
55
|
+
// Output type (after parsing)
|
|
56
|
+
type CreateUserOutput = z.output<typeof createUserSchema>;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Common Validators
|
|
62
|
+
|
|
63
|
+
### Strings
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
z.string().email(); // Email format
|
|
67
|
+
z.string().url(); // URL format
|
|
68
|
+
z.string().uuid(); // UUID format
|
|
69
|
+
z.string().cuid(); // CUID format
|
|
70
|
+
z.string().min(1); // Non-empty
|
|
71
|
+
z.string().max(255); // Max length
|
|
72
|
+
z.string().regex(/^[a-z]+$/); // Custom regex
|
|
73
|
+
z.string().trim(); // Trim whitespace
|
|
74
|
+
z.string().toLowerCase(); // Transform to lowercase
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Numbers
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
z.number().int(); // Integer only
|
|
81
|
+
z.number().positive(); // > 0
|
|
82
|
+
z.number().nonnegative(); // >= 0
|
|
83
|
+
z.number().min(0).max(100); // Range
|
|
84
|
+
z.number().multipleOf(5); // Divisible by 5
|
|
85
|
+
z.number().finite(); // No Infinity
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Objects
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Required & optional
|
|
92
|
+
z.object({
|
|
93
|
+
required: z.string(),
|
|
94
|
+
optional: z.string().optional(),
|
|
95
|
+
nullable: z.string().nullable(),
|
|
96
|
+
default: z.string().default('value'),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Passthrough unknown keys
|
|
100
|
+
z.object({ name: z.string() }).passthrough();
|
|
101
|
+
|
|
102
|
+
// Strip unknown keys (default)
|
|
103
|
+
z.object({ name: z.string() }).strict();
|
|
104
|
+
|
|
105
|
+
// Extend schema
|
|
106
|
+
const baseSchema = z.object({ id: z.string() });
|
|
107
|
+
const extendedSchema = baseSchema.extend({ name: z.string() });
|
|
108
|
+
|
|
109
|
+
// Merge schemas
|
|
110
|
+
const merged = schemaA.merge(schemaB);
|
|
111
|
+
|
|
112
|
+
// Pick/omit fields
|
|
113
|
+
const partial = schema.pick({ name: true, email: true });
|
|
114
|
+
const omitted = schema.omit({ password: true });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Arrays
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
z.array(z.string()); // String array
|
|
121
|
+
z.array(z.number()).min(1); // Non-empty array
|
|
122
|
+
z.array(z.object({})).max(10); // Max 10 items
|
|
123
|
+
z.string().array(); // Alternative syntax
|
|
124
|
+
z.tuple([z.string(), z.number()]); // Fixed tuple
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Transformations
|
|
130
|
+
|
|
131
|
+
### Transform
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const slugSchema = z.string().transform((val) => val.toLowerCase().replace(/\s+/g, '-'));
|
|
135
|
+
|
|
136
|
+
slugSchema.parse('Hello World'); // 'hello-world'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Coerce (Parse from string)
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Useful for form data / URL params
|
|
143
|
+
z.coerce.number(); // "123" -> 123
|
|
144
|
+
z.coerce.boolean(); // "true" -> true
|
|
145
|
+
z.coerce.date(); // "2024-01-01" -> Date
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Preprocess
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const trimmedString = z.preprocess(
|
|
152
|
+
(val) => (typeof val === 'string' ? val.trim() : val),
|
|
153
|
+
z.string().min(1)
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Refinements
|
|
160
|
+
|
|
161
|
+
### Simple Refinement
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const passwordSchema = z
|
|
165
|
+
.string()
|
|
166
|
+
.min(8)
|
|
167
|
+
.refine((val) => /[A-Z]/.test(val), {
|
|
168
|
+
message: 'Must contain uppercase letter',
|
|
169
|
+
})
|
|
170
|
+
.refine((val) => /[0-9]/.test(val), {
|
|
171
|
+
message: 'Must contain number',
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Super Refine (Multiple Errors)
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const passwordSchema = z.string().superRefine((val, ctx) => {
|
|
179
|
+
if (val.length < 8) {
|
|
180
|
+
ctx.addIssue({
|
|
181
|
+
code: z.ZodIssueCode.too_small,
|
|
182
|
+
minimum: 8,
|
|
183
|
+
type: 'string',
|
|
184
|
+
inclusive: true,
|
|
185
|
+
message: 'Password must be at least 8 characters',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (!/[A-Z]/.test(val)) {
|
|
189
|
+
ctx.addIssue({
|
|
190
|
+
code: z.ZodIssueCode.custom,
|
|
191
|
+
message: 'Must contain uppercase letter',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Cross-field Validation
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const registerSchema = z
|
|
201
|
+
.object({
|
|
202
|
+
password: z.string().min(8),
|
|
203
|
+
confirmPassword: z.string(),
|
|
204
|
+
})
|
|
205
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
206
|
+
message: "Passwords don't match",
|
|
207
|
+
path: ['confirmPassword'],
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Error Handling
|
|
214
|
+
|
|
215
|
+
### Safe Parse
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const result = userSchema.safeParse(input);
|
|
219
|
+
|
|
220
|
+
if (result.success) {
|
|
221
|
+
// result.data is typed as User
|
|
222
|
+
console.log(result.data);
|
|
223
|
+
} else {
|
|
224
|
+
// result.error is ZodError
|
|
225
|
+
console.log(result.error.issues);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Custom Error Messages
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const schema = z.object({
|
|
233
|
+
email: z
|
|
234
|
+
.string({
|
|
235
|
+
required_error: 'Email is required',
|
|
236
|
+
invalid_type_error: 'Email must be a string',
|
|
237
|
+
})
|
|
238
|
+
.email({ message: 'Invalid email format' }),
|
|
239
|
+
|
|
240
|
+
age: z
|
|
241
|
+
.number({
|
|
242
|
+
required_error: 'Age is required',
|
|
243
|
+
})
|
|
244
|
+
.min(18, { message: 'Must be 18 or older' }),
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Format Errors
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { z } from 'zod';
|
|
252
|
+
|
|
253
|
+
function formatZodErrors(error: z.ZodError): Record<string, string[]> {
|
|
254
|
+
const formatted: Record<string, string[]> = {};
|
|
255
|
+
|
|
256
|
+
for (const issue of error.issues) {
|
|
257
|
+
const path = issue.path.join('.');
|
|
258
|
+
if (!formatted[path]) {
|
|
259
|
+
formatted[path] = [];
|
|
260
|
+
}
|
|
261
|
+
formatted[path].push(issue.message);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return formatted;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Usage
|
|
268
|
+
const result = schema.safeParse(input);
|
|
269
|
+
if (!result.success) {
|
|
270
|
+
const errors = formatZodErrors(result.error);
|
|
271
|
+
// { email: ['Invalid email format'], password: ['Too short'] }
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Integration Patterns
|
|
278
|
+
|
|
279
|
+
### tRPC Procedures
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { z } from 'zod';
|
|
283
|
+
import { publicProcedure, protectedProcedure } from '../trpc';
|
|
284
|
+
|
|
285
|
+
export const userRouter = {
|
|
286
|
+
getById: publicProcedure.input(z.object({ id: z.string().uuid() })).query(async ({ input }) => {
|
|
287
|
+
return db.user.findUnique({ where: { id: input.id } });
|
|
288
|
+
}),
|
|
289
|
+
|
|
290
|
+
create: protectedProcedure.input(createUserSchema).mutation(async ({ input, ctx }) => {
|
|
291
|
+
return db.user.create({ data: { ...input, createdBy: ctx.user.id } });
|
|
292
|
+
}),
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Server Actions (Next.js)
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
'use server';
|
|
300
|
+
|
|
301
|
+
import { z } from 'zod';
|
|
302
|
+
|
|
303
|
+
const formSchema = z.object({
|
|
304
|
+
name: z.string().min(2),
|
|
305
|
+
email: z.string().email(),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
export async function submitForm(formData: FormData) {
|
|
309
|
+
const parsed = formSchema.safeParse({
|
|
310
|
+
name: formData.get('name'),
|
|
311
|
+
email: formData.get('email'),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
if (!parsed.success) {
|
|
315
|
+
return { error: parsed.error.flatten() };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Process valid data
|
|
319
|
+
await saveToDatabase(parsed.data);
|
|
320
|
+
return { success: true };
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### API Route Validation
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { z } from 'zod';
|
|
328
|
+
import { NextResponse } from 'next/server';
|
|
329
|
+
|
|
330
|
+
const bodySchema = z.object({
|
|
331
|
+
title: z.string().min(1),
|
|
332
|
+
content: z.string(),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
export async function POST(request: Request) {
|
|
336
|
+
const body = await request.json();
|
|
337
|
+
const parsed = bodySchema.safeParse(body);
|
|
338
|
+
|
|
339
|
+
if (!parsed.success) {
|
|
340
|
+
return NextResponse.json({ errors: parsed.error.flatten() }, { status: 400 });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Use parsed.data
|
|
344
|
+
const result = await createPost(parsed.data);
|
|
345
|
+
return NextResponse.json(result, { status: 201 });
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Schema Organization
|
|
352
|
+
|
|
353
|
+
### Structure
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
types/
|
|
357
|
+
├── schemas/
|
|
358
|
+
│ ├── user.schema.ts
|
|
359
|
+
│ ├── post.schema.ts
|
|
360
|
+
│ └── common.schema.ts
|
|
361
|
+
└── index.ts
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Reusable Schemas
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// types/schemas/common.schema.ts
|
|
368
|
+
export const idSchema = z.string().uuid();
|
|
369
|
+
export const emailSchema = z.string().email().toLowerCase();
|
|
370
|
+
export const timestampSchema = z.date().or(z.string().datetime());
|
|
371
|
+
|
|
372
|
+
export const paginationSchema = z.object({
|
|
373
|
+
page: z.coerce.number().int().positive().default(1),
|
|
374
|
+
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Agent Integration
|
|
381
|
+
|
|
382
|
+
This skill is used by:
|
|
383
|
+
|
|
384
|
+
- **zod-schema-designer** agent
|
|
385
|
+
- **zod-validator** agent
|
|
386
|
+
- **input-sanitizer** agent
|
|
387
|
+
- **security-auditor** for validation checks
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## FORBIDDEN
|
|
392
|
+
|
|
393
|
+
1. **`parse()` without try/catch** - Use `safeParse()` instead
|
|
394
|
+
2. **Missing validation on API inputs** - ALWAYS validate
|
|
395
|
+
3. **Inline schemas** - Define in types/ folder
|
|
396
|
+
4. **Generic error messages** - Be specific
|
|
397
|
+
5. **Skipping coercion for form data** - Use `z.coerce`
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Version
|
|
402
|
+
|
|
403
|
+
- **v1.0.0** - Initial implementation based on Zod 3.x patterns
|