Added AI skills
This commit is contained in:
144
.agents/skills/zod/references/compose-intersection.md
Normal file
144
.agents/skills/zod/references/compose-intersection.md
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
title: Use intersection() for Type Combinations
|
||||
impact: MEDIUM
|
||||
impactDescription: Manual field combination loses type relationships; intersection creates proper TypeScript intersection types
|
||||
tags: compose, intersection, and, combination
|
||||
---
|
||||
|
||||
## Use intersection() for Type Combinations
|
||||
|
||||
When you need an object that satisfies multiple schemas simultaneously (like combining a base type with mixins), use `.and()` or `z.intersection()`. This creates proper TypeScript intersection types and validates against all schemas.
|
||||
|
||||
**Incorrect (manual combination):**
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
const timestampsSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
})
|
||||
|
||||
const softDeleteSchema = z.object({
|
||||
deletedAt: z.date().nullable(),
|
||||
deletedBy: z.string().nullable(),
|
||||
})
|
||||
|
||||
const userSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
})
|
||||
|
||||
// Manual combination - verbose and error-prone
|
||||
const fullUserSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
deletedAt: z.date().nullable(),
|
||||
deletedBy: z.string().nullable(),
|
||||
})
|
||||
```
|
||||
|
||||
**Correct (using intersection):**
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
const timestampsSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
})
|
||||
|
||||
const softDeleteSchema = z.object({
|
||||
deletedAt: z.date().nullable(),
|
||||
deletedBy: z.string().nullable(),
|
||||
})
|
||||
|
||||
const userSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
})
|
||||
|
||||
// Using .and() for intersection
|
||||
const fullUserSchema = userSchema
|
||||
.and(timestampsSchema)
|
||||
.and(softDeleteSchema)
|
||||
|
||||
// Or using z.intersection()
|
||||
const fullUserSchema2 = z.intersection(
|
||||
z.intersection(userSchema, timestampsSchema),
|
||||
softDeleteSchema
|
||||
)
|
||||
|
||||
type FullUser = z.infer<typeof fullUserSchema>
|
||||
// {
|
||||
// id: string;
|
||||
// name: string;
|
||||
// email: string;
|
||||
// createdAt: Date;
|
||||
// updatedAt: Date;
|
||||
// deletedAt: Date | null;
|
||||
// deletedBy: string | null;
|
||||
// }
|
||||
```
|
||||
|
||||
**Creating mixins:**
|
||||
|
||||
```typescript
|
||||
// Reusable mixins
|
||||
const auditable = z.object({
|
||||
createdBy: z.string(),
|
||||
updatedBy: z.string(),
|
||||
})
|
||||
|
||||
const versioned = z.object({
|
||||
version: z.number().int().positive(),
|
||||
})
|
||||
|
||||
const tagged = z.object({
|
||||
tags: z.array(z.string()),
|
||||
})
|
||||
|
||||
// Apply mixins to any schema
|
||||
function withAudit<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
|
||||
return schema.and(auditable).and(timestampsSchema)
|
||||
}
|
||||
|
||||
function withVersioning<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
|
||||
return schema.and(versioned)
|
||||
}
|
||||
|
||||
// Usage
|
||||
const documentSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
content: z.string(),
|
||||
})
|
||||
|
||||
const fullDocumentSchema = withAudit(withVersioning(documentSchema))
|
||||
```
|
||||
|
||||
**Intersection vs Merge:**
|
||||
|
||||
```typescript
|
||||
// .merge() - replaces fields from first with second
|
||||
const a = z.object({ x: z.string(), y: z.number() })
|
||||
const b = z.object({ y: z.string() }) // y is string, not number
|
||||
|
||||
a.merge(b) // { x: string, y: string } - b's y wins
|
||||
|
||||
// .and() - requires fields to be compatible
|
||||
// If both have y with different types, intersection fails at runtime
|
||||
a.and(b) // Validation will fail - y can't be both number and string
|
||||
```
|
||||
|
||||
**When NOT to use this pattern:**
|
||||
- When schemas have overlapping fields with different types (use merge)
|
||||
- When you need to override fields (use extend)
|
||||
- Simple cases where extend works fine
|
||||
|
||||
Reference: [Zod API - intersection](https://zod.dev/api#intersection)
|
||||
Reference in New Issue
Block a user