Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:07:42 +08:00
commit 18238e2df8
11 changed files with 4109 additions and 0 deletions

View File

@@ -0,0 +1,779 @@
# Astro DB Patterns Skill
Common patterns and examples for using Astro DB, including schema design, queries, and data seeding.
## When to Use This Skill
- Setting up Astro DB in a project
- Designing database schemas
- Writing queries with Drizzle ORM
- Seeding development data
- Building API endpoints with database access
## Database Setup
### Install Astro DB
```bash
npx astro add db
```
### Configuration File
Create `db/config.ts`:
```typescript
import { defineDb, defineTable, column } from 'astro:db';
const MyTable = defineTable({
columns: {
id: column.number({ primaryKey: true }),
// ... other columns
}
});
export default defineDb({
tables: { MyTable }
});
```
## Column Types
### Text Columns
```typescript
// Basic text
name: column.text()
// Unique text
email: column.text({ unique: true })
// Optional text
bio: column.text({ optional: true })
// Text with default
status: column.text({ default: 'active' })
```
### Number Columns
```typescript
// Basic number
age: column.number()
// Primary key
id: column.number({ primaryKey: true })
// Optional number
rating: column.number({ optional: true })
// Number with default
views: column.number({ default: 0 })
// Unique number
userId: column.number({ unique: true })
```
### Boolean Columns
```typescript
// Basic boolean
published: column.boolean()
// Boolean with default
active: column.boolean({ default: true })
featured: column.boolean({ default: false })
// Optional boolean
verified: column.boolean({ optional: true })
```
### Date Columns
```typescript
// Basic date
createdAt: column.date()
// Optional date
publishedAt: column.date({ optional: true })
// Date with default (requires runtime value)
updatedAt: column.date({ default: new Date() })
```
### JSON Columns
```typescript
// JSON data
metadata: column.json()
// JSON with default
settings: column.json({ default: {} })
options: column.json({ default: [] })
// Optional JSON
extra: column.json({ optional: true })
```
## Common Schema Patterns
### Blog Database
```typescript
import { defineDb, defineTable, column } from 'astro:db';
const Author = defineTable({
columns: {
id: column.number({ primaryKey: true }),
name: column.text(),
email: column.text({ unique: true }),
bio: column.text({ optional: true }),
avatar: column.text({ optional: true }),
website: column.text({ optional: true }),
createdAt: column.date()
}
});
const Post = defineTable({
columns: {
id: column.number({ primaryKey: true }),
title: column.text(),
slug: column.text({ unique: true }),
content: column.text(),
excerpt: column.text({ optional: true }),
published: column.boolean({ default: false }),
publishedAt: column.date({ optional: true }),
updatedAt: column.date({ optional: true }),
authorId: column.number(),
views: column.number({ default: 0 }),
featured: column.boolean({ default: false })
}
});
const Tag = defineTable({
columns: {
id: column.number({ primaryKey: true }),
name: column.text({ unique: true }),
slug: column.text({ unique: true })
}
});
const PostTag = defineTable({
columns: {
postId: column.number(),
tagId: column.number()
}
});
const Comment = defineTable({
columns: {
id: column.number({ primaryKey: true }),
postId: column.number(),
author: column.text(),
email: column.text(),
content: column.text(),
approved: column.boolean({ default: false }),
createdAt: column.date()
}
});
export default defineDb({
tables: { Author, Post, Tag, PostTag, Comment }
});
```
### E-commerce Database
```typescript
const Product = defineTable({
columns: {
id: column.number({ primaryKey: true }),
name: column.text(),
slug: column.text({ unique: true }),
description: column.text(),
price: column.number(),
salePrice: column.number({ optional: true }),
sku: column.text({ unique: true }),
inStock: column.boolean({ default: true }),
quantity: column.number({ default: 0 }),
categoryId: column.number(),
images: column.json({ default: [] }),
createdAt: column.date()
}
});
const Category = defineTable({
columns: {
id: column.number({ primaryKey: true }),
name: column.text(),
slug: column.text({ unique: true }),
description: column.text({ optional: true }),
parentId: column.number({ optional: true })
}
});
const Order = defineTable({
columns: {
id: column.number({ primaryKey: true }),
orderNumber: column.text({ unique: true }),
customerId: column.number(),
total: column.number(),
status: column.text({ default: 'pending' }),
createdAt: column.date(),
completedAt: column.date({ optional: true })
}
});
const OrderItem = defineTable({
columns: {
id: column.number({ primaryKey: true }),
orderId: column.number(),
productId: column.number(),
quantity: column.number(),
price: column.number()
}
});
export default defineDb({
tables: { Product, Category, Order, OrderItem }
});
```
### User Management Database
```typescript
const User = defineTable({
columns: {
id: column.number({ primaryKey: true }),
username: column.text({ unique: true }),
email: column.text({ unique: true }),
passwordHash: column.text(),
firstName: column.text(),
lastName: column.text(),
role: column.text({ default: 'user' }),
active: column.boolean({ default: true }),
emailVerified: column.boolean({ default: false }),
lastLogin: column.date({ optional: true }),
createdAt: column.date()
}
});
const Session = defineTable({
columns: {
id: column.text({ primaryKey: true }),
userId: column.number(),
expiresAt: column.date(),
createdAt: column.date()
}
});
const UserProfile = defineTable({
columns: {
userId: column.number({ unique: true }),
bio: column.text({ optional: true }),
avatar: column.text({ optional: true }),
location: column.text({ optional: true }),
website: column.text({ optional: true }),
social: column.json({ default: {} })
}
});
export default defineDb({
tables: { User, Session, UserProfile }
});
```
## Data Seeding
### Basic Seed File
Create `db/seed.ts`:
```typescript
import { db, Author, Post, Tag } from 'astro:db';
export default async function seed() {
// Insert authors
await db.insert(Author).values([
{
id: 1,
name: 'John Doe',
email: 'john@example.com',
bio: 'Tech enthusiast and blogger',
createdAt: new Date('2024-01-01')
},
{
id: 2,
name: 'Jane Smith',
email: 'jane@example.com',
bio: 'Software developer',
createdAt: new Date('2024-01-01')
}
]);
// Insert tags
await db.insert(Tag).values([
{ id: 1, name: 'Astro', slug: 'astro' },
{ id: 2, name: 'JavaScript', slug: 'javascript' },
{ id: 3, name: 'Tutorial', slug: 'tutorial' }
]);
// Insert posts
await db.insert(Post).values([
{
id: 1,
title: 'Getting Started with Astro',
slug: 'getting-started-astro',
content: 'Full post content here...',
excerpt: 'Learn the basics of Astro',
published: true,
publishedAt: new Date('2024-01-15'),
authorId: 1,
views: 150,
featured: true
},
{
id: 2,
title: 'Advanced Astro Patterns',
slug: 'advanced-astro',
content: 'Advanced content...',
published: false,
authorId: 2,
views: 0
}
]);
}
```
### Seed with Relationships
```typescript
import { db, Post, Tag, PostTag, Comment } from 'astro:db';
export default async function seed() {
// ... insert posts and tags ...
// Link posts to tags (many-to-many)
await db.insert(PostTag).values([
{ postId: 1, tagId: 1 }, // Post 1 -> Astro
{ postId: 1, tagId: 3 }, // Post 1 -> Tutorial
{ postId: 2, tagId: 1 }, // Post 2 -> Astro
{ postId: 2, tagId: 2 } // Post 2 -> JavaScript
]);
// Add comments
await db.insert(Comment).values([
{
id: 1,
postId: 1,
author: 'Reader',
email: 'reader@example.com',
content: 'Great post!',
approved: true,
createdAt: new Date()
}
]);
}
```
## Query Patterns
### Select All
```typescript
import { db, Post } from 'astro:db';
const allPosts = await db.select().from(Post);
```
### Select with Filter
```typescript
import { db, Post, eq } from 'astro:db';
// Published posts
const publishedPosts = await db
.select()
.from(Post)
.where(eq(Post.published, true));
// Post by slug
const post = await db
.select()
.from(Post)
.where(eq(Post.slug, 'my-post'))
.get(); // Returns single result or undefined
```
### Multiple Conditions
```typescript
import { db, Post, eq, gt, and, or } from 'astro:db';
// AND condition
const featuredPublished = await db
.select()
.from(Post)
.where(and(
eq(Post.published, true),
eq(Post.featured, true)
));
// OR condition
const popularOrFeatured = await db
.select()
.from(Post)
.where(or(
gt(Post.views, 1000),
eq(Post.featured, true)
));
```
### Comparison Operators
```typescript
import { db, Post, gt, gte, lt, lte, ne, like } from 'astro:db';
// Greater than
const popularPosts = await db
.select()
.from(Post)
.where(gt(Post.views, 100));
// Greater than or equal
const recentPosts = await db
.select()
.from(Post)
.where(gte(Post.publishedAt, new Date('2024-01-01')));
// Less than
const drafts = await db
.select()
.from(Post)
.where(lt(Post.views, 10));
// Not equal
const notDrafts = await db
.select()
.from(Post)
.where(ne(Post.published, false));
// LIKE pattern matching
const astroPosts = await db
.select()
.from(Post)
.where(like(Post.title, '%Astro%'));
```
### Ordering Results
```typescript
import { db, Post, desc, asc } from 'astro:db';
// Descending order
const latestPosts = await db
.select()
.from(Post)
.orderBy(desc(Post.publishedAt));
// Ascending order
const oldestFirst = await db
.select()
.from(Post)
.orderBy(asc(Post.createdAt));
// Multiple order columns
const sorted = await db
.select()
.from(Post)
.orderBy(desc(Post.featured), desc(Post.publishedAt));
```
### Limit and Offset
```typescript
import { db, Post, desc } from 'astro:db';
// Latest 10 posts
const latest = await db
.select()
.from(Post)
.orderBy(desc(Post.publishedAt))
.limit(10);
// Pagination
const page = 2;
const perPage = 10;
const paginated = await db
.select()
.from(Post)
.limit(perPage)
.offset((page - 1) * perPage);
```
### Joins
```typescript
import { db, Post, Author, eq } from 'astro:db';
// Inner join
const postsWithAuthors = await db
.select()
.from(Post)
.innerJoin(Author, eq(Post.authorId, Author.id));
// Access joined data
postsWithAuthors.forEach(row => {
console.log(row.Post.title);
console.log(row.Author.name);
});
```
### Complex Join Query
```typescript
import { db, Post, Author, Tag, PostTag, eq } from 'astro:db';
// Posts with authors and tags
const postsWithDetails = await db
.select({
post: Post,
author: Author,
tag: Tag
})
.from(Post)
.innerJoin(Author, eq(Post.authorId, Author.id))
.innerJoin(PostTag, eq(Post.id, PostTag.postId))
.innerJoin(Tag, eq(PostTag.tagId, Tag.id));
```
## Insert, Update, Delete
### Insert Single Record
```typescript
import { db, Post } from 'astro:db';
await db.insert(Post).values({
title: 'New Post',
slug: 'new-post',
content: 'Content here',
authorId: 1,
published: false
});
```
### Insert Multiple Records
```typescript
await db.insert(Tag).values([
{ name: 'Tag 1', slug: 'tag-1' },
{ name: 'Tag 2', slug: 'tag-2' },
{ name: 'Tag 3', slug: 'tag-3' }
]);
```
### Update Records
```typescript
import { db, Post, eq } from 'astro:db';
// Update single field
await db
.update(Post)
.set({ views: 100 })
.where(eq(Post.id, 1));
// Update multiple fields
await db
.update(Post)
.set({
published: true,
publishedAt: new Date(),
updatedAt: new Date()
})
.where(eq(Post.slug, 'my-post'));
```
### Delete Records
```typescript
import { db, Post, Comment, eq, lt } from 'astro:db';
// Delete by ID
await db
.delete(Post)
.where(eq(Post.id, 1));
// Delete with condition
await db
.delete(Comment)
.where(lt(Comment.createdAt, new Date('2024-01-01')));
```
## API Endpoint Patterns
### GET All Items
```typescript
// src/pages/api/posts.json.ts
import type { APIRoute } from 'astro';
import { db, Post, desc } from 'astro:db';
export const GET: APIRoute = async () => {
const posts = await db
.select()
.from(Post)
.orderBy(desc(Post.publishedAt));
return new Response(JSON.stringify(posts), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
};
```
### GET Single Item
```typescript
// src/pages/api/posts/[slug].json.ts
import type { APIRoute } from 'astro';
import { db, Post, eq } from 'astro:db';
export const GET: APIRoute = async ({ params }) => {
const post = await db
.select()
.from(Post)
.where(eq(Post.slug, params.slug))
.get();
if (!post) {
return new Response(JSON.stringify({ error: 'Not found' }), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}
return new Response(JSON.stringify(post), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
};
```
### POST Create Item
```typescript
// src/pages/api/posts.json.ts
import type { APIRoute } from 'astro';
import { db, Post } from 'astro:db';
export const POST: APIRoute = async ({ request }) => {
try {
const data = await request.json();
await db.insert(Post).values({
title: data.title,
slug: data.slug,
content: data.content,
authorId: data.authorId,
published: false,
createdAt: new Date()
});
return new Response(JSON.stringify({ success: true }), {
status: 201,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
};
```
### PUT/PATCH Update Item
```typescript
// src/pages/api/posts/[id].json.ts
import type { APIRoute } from 'astro';
import { db, Post, eq } from 'astro:db';
export const PUT: APIRoute = async ({ params, request }) => {
const data = await request.json();
const id = parseInt(params.id);
await db
.update(Post)
.set({
...data,
updatedAt: new Date()
})
.where(eq(Post.id, id));
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
};
```
### DELETE Item
```typescript
export const DELETE: APIRoute = async ({ params }) => {
const id = parseInt(params.id);
await db.delete(Post).where(eq(Post.id, id));
return new Response(null, { status: 204 });
};
```
## Production Deployment
### Environment Variables
```env
# .env
ASTRO_DB_REMOTE_URL=libsql://your-db.turso.io
ASTRO_DB_APP_TOKEN=your-auth-token
```
### Push Schema to Production
```bash
astro db push --remote
```
### Verify Remote Connection
```bash
astro db verify --remote
```
## Best Practices
1. **Use Primary Keys**: Always define a primary key for each table
2. **Unique Constraints**: Use `unique: true` for fields like email, slug
3. **Default Values**: Provide sensible defaults for boolean/number fields
4. **Optional Fields**: Mark truly optional fields with `optional: true`
5. **Indexing**: Use unique constraints for fields you'll query often
6. **Relationships**: Use foreign keys (number columns) to link tables
7. **Dates**: Always use `column.date()` for timestamp fields
8. **JSON**: Use JSON columns for flexible/nested data
9. **Naming**: Use consistent naming (camelCase for columns, PascalCase for tables)
10. **Seeding**: Keep seed data representative and minimal
## Tips
- Dev server auto-restarts on schema changes
- Check `.astro/content.db` for local database
- Use `.get()` for single results, omit for arrays
- Drizzle ORM provides type-safe queries
- Test queries in seed file first
- Use transactions for related inserts
- Consider indexes for frequently queried fields
- Migrate carefully in production

View File

@@ -0,0 +1,584 @@
# Astro Frontmatter Schemas Skill
Common Zod schema patterns and frontmatter examples for Astro content collections.
## When to Use This Skill
- Designing content collection schemas
- Creating frontmatter for markdown files
- Validating content structure
- Setting up type-safe content
## Common Schema Patterns
### Blog Post Schema
```typescript
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
// Core Fields
title: z.string().max(80),
description: z.string().max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
// Author
author: z.string(),
authorUrl: z.string().url().optional(),
// Categorization
tags: z.array(z.string()).default([]),
category: z.enum(['tutorial', 'news', 'guide', 'review']).optional(),
// Publishing
draft: z.boolean().default(false),
featured: z.boolean().default(false),
// Media
image: z.object({
url: z.string(),
alt: z.string()
}).optional(),
// SEO
canonicalURL: z.string().url().optional(),
// Metadata
readingTime: z.number().optional(),
relatedPosts: z.array(z.string()).default([])
})
});
```
**Corresponding Frontmatter:**
```yaml
---
title: 'Getting Started with Astro'
description: 'Learn the fundamentals of building fast websites with Astro'
pubDate: 2024-01-15
updatedDate: 2024-01-20
author: 'John Doe'
authorUrl: 'https://example.com/john'
tags: ['astro', 'tutorial', 'web-dev']
category: 'tutorial'
draft: false
featured: true
image:
url: './images/hero.jpg'
alt: 'Astro logo with stars'
canonicalURL: 'https://example.com/blog/getting-started'
readingTime: 8
relatedPosts: ['advanced-astro', 'astro-tips']
---
```
### Documentation Schema
```typescript
const docs = defineCollection({
loader: glob({ pattern: "**/*.mdx", base: "./src/content/docs" }),
schema: z.object({
title: z.string(),
description: z.string(),
// Sidebar
sidebar: z.object({
order: z.number(),
label: z.string().optional(),
hidden: z.boolean().default(false),
badge: z.string().optional()
}).optional(),
// Status
lastUpdated: z.coerce.date().optional(),
version: z.string().optional(),
deprecated: z.boolean().default(false),
// Contributors
contributors: z.array(z.string()).default([]),
// Navigation
prev: z.object({
text: z.string(),
link: z.string()
}).optional(),
next: z.object({
text: z.string(),
link: z.string()
}).optional(),
// Table of Contents
tableOfContents: z.boolean().default(true),
tocDepth: z.number().min(1).max(6).default(3)
})
});
```
**Corresponding Frontmatter:**
```yaml
---
title: 'API Reference'
description: 'Complete API documentation for the core library'
sidebar:
order: 2
label: 'API Docs'
hidden: false
badge: 'New'
lastUpdated: 2024-01-20
version: '2.1.0'
deprecated: false
contributors: ['john-doe', 'jane-smith']
prev:
text: 'Installation'
link: '/docs/installation'
next:
text: 'Configuration'
link: '/docs/configuration'
tableOfContents: true
tocDepth: 3
---
```
### Product Schema
```typescript
const products = defineCollection({
loader: glob({ pattern: "**/*.json", base: "./src/content/products" }),
schema: z.object({
// Basic Info
name: z.string(),
description: z.string(),
shortDescription: z.string().max(100).optional(),
// Pricing
price: z.number().positive(),
currency: z.string().default('USD'),
salePrice: z.number().positive().optional(),
// Categorization
category: z.enum(['software', 'hardware', 'service', 'digital']),
tags: z.array(z.string()).default([]),
// Inventory
sku: z.string(),
inStock: z.boolean().default(true),
quantity: z.number().int().min(0).optional(),
// Features
features: z.array(z.string()),
specifications: z.record(z.string()).optional(),
// Media
images: z.array(z.object({
url: z.string(),
alt: z.string(),
primary: z.boolean().default(false)
})),
// Rating
rating: z.number().min(0).max(5).optional(),
reviewCount: z.number().int().min(0).default(0)
})
});
```
**Corresponding JSON:**
```json
{
"name": "Premium Web Hosting",
"description": "Fast, reliable web hosting with 99.9% uptime",
"shortDescription": "Premium hosting solution",
"price": 29.99,
"currency": "USD",
"salePrice": 19.99,
"category": "service",
"tags": ["hosting", "cloud", "premium"],
"sku": "HOST-001",
"inStock": true,
"quantity": 100,
"features": [
"99.9% uptime guarantee",
"Free SSL certificate",
"24/7 support"
],
"specifications": {
"storage": "100GB SSD",
"bandwidth": "Unlimited",
"domains": "1"
},
"images": [
{
"url": "/images/hosting-main.jpg",
"alt": "Server rack",
"primary": true
}
],
"rating": 4.5,
"reviewCount": 127
}
```
### Team Member Schema
```typescript
const team = defineCollection({
loader: glob({ pattern: "**/*.yml", base: "./src/content/team" }),
schema: z.object({
name: z.string(),
role: z.string(),
department: z.enum(['engineering', 'design', 'marketing', 'sales']).optional(),
bio: z.string(),
avatar: z.string(),
social: z.object({
twitter: z.string().url().optional(),
github: z.string().url().optional(),
linkedin: z.string().url().optional(),
website: z.string().url().optional()
}).optional(),
startDate: z.coerce.date(),
location: z.string().optional(),
skills: z.array(z.string()).default([]),
featured: z.boolean().default(false)
})
});
```
**Corresponding YAML:**
```yaml
name: 'Jane Smith'
role: 'Senior Software Engineer'
department: 'engineering'
bio: 'Full-stack developer with 10 years experience building scalable web applications.'
avatar: '/images/team/jane.jpg'
social:
twitter: 'https://twitter.com/janesmith'
github: 'https://github.com/janesmith'
linkedin: 'https://linkedin.com/in/janesmith'
website: 'https://janesmith.dev'
startDate: 2020-03-15
location: 'San Francisco, CA'
skills: ['TypeScript', 'React', 'Node.js', 'AWS']
featured: true
```
### Project/Portfolio Schema
```typescript
const projects = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/projects" }),
schema: z.object({
title: z.string(),
description: z.string(),
technologies: z.array(z.string()),
liveUrl: z.string().url().optional(),
githubUrl: z.string().url().optional(),
demoUrl: z.string().url().optional(),
thumbnail: z.string(),
images: z.array(z.string()).default([]),
date: z.coerce.date(),
endDate: z.coerce.date().optional(),
status: z.enum(['active', 'completed', 'archived']).default('active'),
featured: z.boolean().default(false),
client: z.string().optional(),
team: z.array(z.string()).default([]),
tags: z.array(z.string()).default([])
})
});
```
**Corresponding Frontmatter:**
```yaml
---
title: 'E-commerce Platform Redesign'
description: 'Complete redesign and modernization of legacy e-commerce platform'
technologies: ['React', 'Next.js', 'TypeScript', 'Tailwind CSS', 'PostgreSQL']
liveUrl: 'https://shop.example.com'
githubUrl: 'https://github.com/example/shop'
thumbnail: './images/shop-thumb.jpg'
images:
- './images/shop-1.jpg'
- './images/shop-2.jpg'
- './images/shop-3.jpg'
date: 2023-06-01
endDate: 2023-12-15
status: 'completed'
featured: true
client: 'Example Corp'
team: ['john-doe', 'jane-smith']
tags: ['web-dev', 'e-commerce', 'react']
---
```
## Zod Validation Patterns
### String Validation
```typescript
// Basic string
title: z.string()
// Length constraints
title: z.string().min(1).max(100)
description: z.string().max(500)
// Email validation
email: z.string().email()
// URL validation
website: z.string().url()
// Regex pattern
slug: z.string().regex(/^[a-z0-9-]+$/)
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/)
// Specific values
status: z.string().refine(val => ['draft', 'published'].includes(val))
```
### Number Validation
```typescript
// Basic number
count: z.number()
// Integer only
age: z.number().int()
// Positive numbers
price: z.number().positive()
// Range
rating: z.number().min(1).max(5)
percentage: z.number().min(0).max(100)
// With coercion (converts strings)
views: z.coerce.number()
```
### Date Validation
```typescript
// With coercion (recommended)
pubDate: z.coerce.date()
// Strict date object
deadline: z.date()
// Date range
startDate: z.coerce.date()
endDate: z.coerce.date().refine(
(date) => date > startDate,
"End date must be after start date"
)
```
### Array Validation
```typescript
// Array of strings
tags: z.array(z.string())
// Minimum items
tags: z.array(z.string()).min(1)
// Maximum items
images: z.array(z.string()).max(10)
// Default value
tags: z.array(z.string()).default([])
// Array of objects
authors: z.array(z.object({
name: z.string(),
email: z.string().email()
}))
```
### Object Validation
```typescript
// Nested object
author: z.object({
name: z.string(),
email: z.string().email(),
url: z.string().url().optional()
})
// Record (dynamic keys)
metadata: z.record(z.string())
settings: z.record(z.boolean())
// Partial object (all fields optional)
options: z.object({
theme: z.string(),
color: z.string()
}).partial()
```
### Enum Validation
```typescript
// Fixed set of values
status: z.enum(['draft', 'published', 'archived'])
priority: z.enum(['low', 'medium', 'high'])
category: z.enum(['news', 'tutorial', 'guide'])
```
### Boolean Validation
```typescript
// Basic boolean
published: z.boolean()
// With default
draft: z.boolean().default(false)
featured: z.boolean().default(false)
```
### Optional and Default Values
```typescript
// Optional field
subtitle: z.string().optional()
updatedDate: z.coerce.date().optional()
// With default value
draft: z.boolean().default(false)
views: z.number().default(0)
tags: z.array(z.string()).default([])
```
### Union Types
```typescript
// Multiple possible types
content: z.union([
z.string(),
z.object({ html: z.string() })
])
// Discriminated union
media: z.discriminatedUnion('type', [
z.object({
type: z.literal('image'),
url: z.string(),
alt: z.string()
}),
z.object({
type: z.literal('video'),
url: z.string(),
duration: z.number()
})
])
```
## Best Practices
1. **Use z.coerce.date()** for date strings in frontmatter
2. **Provide defaults** for booleans and arrays
3. **Use enums** for fixed sets of values
4. **Validate formats** with built-in validators (email, url)
5. **Add constraints** (min, max) for better validation
6. **Use optional()** for truly optional fields
7. **Document schemas** with TypeScript comments
8. **Keep schemas DRY** by extracting common patterns
## Common Frontmatter Patterns
### Minimal Blog Post
```yaml
---
title: 'Post Title'
description: 'Post description'
pubDate: 2024-01-15
author: 'Author Name'
tags: ['tag1', 'tag2']
---
```
### Full-Featured Blog Post
```yaml
---
title: 'Comprehensive Post Title'
description: 'Detailed SEO-optimized description'
pubDate: 2024-01-15
updatedDate: 2024-01-20
author: 'Author Name'
authorUrl: 'https://author.com'
tags: ['web-dev', 'tutorial']
category: 'tutorial'
draft: false
featured: true
image:
url: './hero.jpg'
alt: 'Hero image description'
canonicalURL: 'https://example.com/original'
readingTime: 10
relatedPosts: ['post-1', 'post-2']
---
```
### Documentation Page
```yaml
---
title: 'Getting Started'
description: 'Introduction to the framework'
sidebar:
order: 1
label: 'Intro'
lastUpdated: 2024-01-15
contributors: ['john', 'jane']
tableOfContents: true
---
```
### Product (JSON)
```json
{
"name": "Product Name",
"description": "Product description",
"price": 99.99,
"category": "software",
"sku": "PROD-001",
"inStock": true,
"features": ["Feature 1", "Feature 2"],
"images": [
{ "url": "/img.jpg", "alt": "Product", "primary": true }
]
}
```
## Tips
- Always match frontmatter to schema exactly
- Use ISO date format: `2024-01-15`
- Quote strings with special characters
- Use arrays for multiple values: `['tag1', 'tag2']`
- Nest objects with proper indentation
- Test with sample content first
- Check generated types in `.astro/types.d.ts`