Initial commit
This commit is contained in:
567
templates/AGENTS_CONFIG.md
Normal file
567
templates/AGENTS_CONFIG.md
Normal file
@@ -0,0 +1,567 @@
|
||||
# AI Agents Configuration: [Project Name]
|
||||
|
||||
**AI Provider**: [OpenAI / Claude / Gemini / Cloudflare AI]
|
||||
**Framework**: [Vercel AI SDK / Custom / Cloudflare Workers AI]
|
||||
**Agent Architecture**: [Single agent / Multi-agent / Agentic workflows]
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines AI agents, their capabilities, tools, and workflows for this project.
|
||||
|
||||
**Agent Philosophy**:
|
||||
- **Purpose-built agents** - Each agent has a specific, well-defined role
|
||||
- **Tool-equipped** - Agents have access to functions they need
|
||||
- **Conversational** - Agents can ask clarifying questions
|
||||
- **Stateful when needed** - Use Durable Objects for long-running conversations
|
||||
- **Fail gracefully** - Always have fallback responses
|
||||
|
||||
---
|
||||
|
||||
## AI Provider Configuration
|
||||
|
||||
### Primary Provider: [Provider Name]
|
||||
|
||||
**Model**: [e.g., gpt-5, claude-sonnet-4-5, gemini-2.5-pro]
|
||||
**API Key**: Stored in environment variable `[KEY_NAME]`
|
||||
**Base URL**: [API endpoint]
|
||||
|
||||
**Configuration**:
|
||||
```typescript
|
||||
// src/lib/ai-config.ts
|
||||
export const aiConfig = {
|
||||
provider: '[provider]',
|
||||
model: '[model-name]',
|
||||
apiKey: process.env.[KEY_NAME],
|
||||
temperature: 0.7,
|
||||
maxTokens: 2000,
|
||||
topP: 1.0
|
||||
}
|
||||
```
|
||||
|
||||
**Fallback Provider** (optional): [Secondary provider if primary fails]
|
||||
|
||||
---
|
||||
|
||||
## Agents
|
||||
|
||||
### Agent 1: [Agent Name]
|
||||
|
||||
**Purpose**: [What this agent does]
|
||||
**Model**: [Specific model if different from default]
|
||||
**Context Window**: [Token limit for this agent]
|
||||
|
||||
**Capabilities**:
|
||||
- [Capability 1]
|
||||
- [Capability 2]
|
||||
- [Capability 3]
|
||||
|
||||
**System Prompt**:
|
||||
```
|
||||
You are [agent role]. Your goal is to [agent purpose].
|
||||
|
||||
Guidelines:
|
||||
- [Guideline 1]
|
||||
- [Guideline 2]
|
||||
- [Guideline 3]
|
||||
|
||||
When you need information you don't have:
|
||||
- Use available tools
|
||||
- Ask the user clarifying questions
|
||||
- Provide your best answer with caveats
|
||||
|
||||
Response format:
|
||||
- Be concise and actionable
|
||||
- Use markdown for formatting
|
||||
- Include code examples when helpful
|
||||
```
|
||||
|
||||
**Available Tools**:
|
||||
- `[tool_name]` - [Description]
|
||||
- `[tool_name]` - [Description]
|
||||
|
||||
**Example Conversation**:
|
||||
```
|
||||
User: [Example input]
|
||||
Agent: [Example response]
|
||||
|
||||
User: [Follow-up]
|
||||
Agent: [Agent uses tool and responds]
|
||||
```
|
||||
|
||||
**Endpoint**: `POST /api/agents/[agent-name]`
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"message": "User message",
|
||||
"conversationId": "optional-conversation-id",
|
||||
"context": { "optional": "context" }
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (streaming):
|
||||
```
|
||||
data: {"type":"text","content":"Agent response..."}
|
||||
data: {"type":"tool_call","name":"search","args":{"query":"..."}}
|
||||
data: {"type":"tool_result","result":{...}}
|
||||
data: {"type":"done"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Agent 2: [Agent Name]
|
||||
|
||||
**Purpose**: [What this agent does]
|
||||
|
||||
[... repeat structure from Agent 1 ...]
|
||||
|
||||
---
|
||||
|
||||
## Tools (Functions)
|
||||
|
||||
AI agents can call these functions to perform actions or retrieve information.
|
||||
|
||||
### Tool: `[tool_name]`
|
||||
|
||||
**Purpose**: [What this tool does]
|
||||
|
||||
**Parameters**:
|
||||
```typescript
|
||||
{
|
||||
param1: string, // Description
|
||||
param2: number, // Description
|
||||
param3?: boolean // Optional description
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```typescript
|
||||
// src/lib/ai-tools.ts
|
||||
export async function [tool_name](params: ToolParams, context: Context) {
|
||||
// Tool logic
|
||||
const result = await performAction(params)
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
// Agent calls tool
|
||||
const result = await [tool_name]({
|
||||
param1: "value",
|
||||
param2: 42
|
||||
})
|
||||
|
||||
// Tool returns
|
||||
{
|
||||
success: true,
|
||||
data: { /* result */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Failure Handling**: [How tool handles errors]
|
||||
|
||||
---
|
||||
|
||||
### Tool: `search_database`
|
||||
|
||||
**Purpose**: Search the database for user-specific information
|
||||
|
||||
**Parameters**:
|
||||
```typescript
|
||||
{
|
||||
query: string, // Natural language search query
|
||||
table: string, // Which table to search
|
||||
limit?: number // Max results (default 5)
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```typescript
|
||||
export async function search_database(
|
||||
{ query, table, limit = 5 }: SearchParams,
|
||||
context: Context
|
||||
) {
|
||||
const userId = context.get('userId')
|
||||
|
||||
// Convert natural language to SQL (simplified example)
|
||||
const results = await context.env.DB.prepare(
|
||||
`SELECT * FROM ${table} WHERE user_id = ? LIMIT ?`
|
||||
).bind(userId, limit).all()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: results.results
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Workflows
|
||||
|
||||
### Workflow: [Workflow Name]
|
||||
|
||||
**Purpose**: [What this workflow accomplishes]
|
||||
|
||||
**Agents Involved**:
|
||||
1. [Agent 1] - [Role in workflow]
|
||||
2. [Agent 2] - [Role in workflow]
|
||||
|
||||
**Flow**:
|
||||
```
|
||||
1. User submits [input]
|
||||
↓
|
||||
2. [Agent 1] analyzes input
|
||||
↓
|
||||
3. [Agent 1] calls tool: [tool_name]
|
||||
↓
|
||||
4. Tool returns data
|
||||
↓
|
||||
5. [Agent 1] generates response
|
||||
↓
|
||||
6. If needed: Hand off to [Agent 2]
|
||||
↓
|
||||
7. [Agent 2] completes task
|
||||
↓
|
||||
8. Return final result to user
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```
|
||||
User: "Find all high-priority tasks and create a summary report"
|
||||
|
||||
[Planner Agent] → Calls search_database(query="high priority tasks")
|
||||
→ Receives 5 tasks
|
||||
→ Hands off to [Writer Agent] with task data
|
||||
|
||||
[Writer Agent] → Generates formatted report
|
||||
→ Returns markdown report to user
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conversation State
|
||||
|
||||
### Stateless Agents (Default)
|
||||
|
||||
**When to use**: Single-turn interactions, no context needed
|
||||
|
||||
**Implementation**: Each request is independent
|
||||
|
||||
**Example**: Simple Q&A, content generation
|
||||
|
||||
---
|
||||
|
||||
### Stateful Agents (Durable Objects)
|
||||
|
||||
**When to use**: Multi-turn conversations, context retention
|
||||
|
||||
**Implementation**: Store conversation history in Durable Object
|
||||
|
||||
**Setup**:
|
||||
```typescript
|
||||
// src/durable-objects/conversation.ts
|
||||
export class Conversation implements DurableObject {
|
||||
private messages: Message[] = []
|
||||
|
||||
async fetch(request: Request) {
|
||||
const { message } = await request.json()
|
||||
|
||||
// Add user message to history
|
||||
this.messages.push({ role: 'user', content: message })
|
||||
|
||||
// Call AI with full conversation history
|
||||
const response = await callAI({
|
||||
messages: this.messages,
|
||||
tools: availableTools
|
||||
})
|
||||
|
||||
// Add assistant response to history
|
||||
this.messages.push({ role: 'assistant', content: response })
|
||||
|
||||
return new Response(JSON.stringify({ response }))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Wrangler Config**:
|
||||
```jsonc
|
||||
{
|
||||
"durable_objects": {
|
||||
"bindings": [
|
||||
{
|
||||
"name": "CONVERSATIONS",
|
||||
"class_name": "Conversation",
|
||||
"script_name": "app"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```typescript
|
||||
// Create/get conversation
|
||||
const conversationId = crypto.randomUUID()
|
||||
const durableObjectId = env.CONVERSATIONS.idFromName(conversationId)
|
||||
const stub = env.CONVERSATIONS.get(durableObjectId)
|
||||
|
||||
// Send message to conversation
|
||||
const response = await stub.fetch(request)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming Responses
|
||||
|
||||
**Why stream**: Better UX, appears faster, shows progress
|
||||
|
||||
**Implementation** (Server-Sent Events):
|
||||
```typescript
|
||||
// src/routes/agents.ts
|
||||
app.post('/api/agents/:agentName', async (c) => {
|
||||
const { message } = await c.req.json()
|
||||
|
||||
const stream = await streamAIResponse({
|
||||
message,
|
||||
agent: c.req.param('agentName')
|
||||
})
|
||||
|
||||
return c.newResponse(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Client** (Vercel AI SDK):
|
||||
```typescript
|
||||
import { useChat } from '@ai-sdk/react'
|
||||
|
||||
export function ChatInterface() {
|
||||
const { messages, input, handleInputChange, handleSubmit } = useChat({
|
||||
api: '/api/agents/chat'
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{messages.map(m => (
|
||||
<div key={m.id}>{m.content}</div>
|
||||
))}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input value={input} onChange={handleInputChange} />
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt Engineering
|
||||
|
||||
### System Prompt Best Practices
|
||||
|
||||
**Structure**:
|
||||
1. **Role definition** - "You are a [role]"
|
||||
2. **Capabilities** - What the agent can do
|
||||
3. **Constraints** - What the agent cannot do
|
||||
4. **Tone/style** - How the agent should respond
|
||||
5. **Output format** - Markdown, JSON, etc
|
||||
|
||||
**Example**:
|
||||
```
|
||||
You are a helpful task management assistant.
|
||||
|
||||
Your capabilities:
|
||||
- Search user's tasks
|
||||
- Create, update, delete tasks
|
||||
- Generate task summaries and reports
|
||||
- Set reminders and priorities
|
||||
|
||||
Your constraints:
|
||||
- Never access other users' data
|
||||
- Always confirm before deleting tasks
|
||||
- Ask for clarification if user intent is unclear
|
||||
|
||||
Response style:
|
||||
- Be concise and actionable
|
||||
- Use bullet points for lists
|
||||
- Include task IDs for reference
|
||||
|
||||
Output format:
|
||||
- Use markdown formatting
|
||||
- Bold important information
|
||||
- Use code blocks for task IDs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Token Management
|
||||
|
||||
**Cost Optimization**:
|
||||
- Use smaller models for simple tasks (gpt-5-mini, claude-haiku-4-5)
|
||||
- Use larger models only when needed (gpt-5, claude-sonnet-4-5)
|
||||
- Limit conversation history (keep last N messages)
|
||||
- Summarize long conversations to reduce tokens
|
||||
|
||||
**Token Budgets**:
|
||||
```typescript
|
||||
const TOKEN_BUDGETS = {
|
||||
'simple-qa': {
|
||||
model: 'gpt-5-mini',
|
||||
maxInputTokens: 500,
|
||||
maxOutputTokens: 500
|
||||
},
|
||||
'complex-analysis': {
|
||||
model: 'gpt-5',
|
||||
maxInputTokens: 4000,
|
||||
maxOutputTokens: 2000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### AI Provider Failures
|
||||
|
||||
**Handle**:
|
||||
- Rate limits (retry with backoff)
|
||||
- API errors (fallback provider or error message)
|
||||
- Timeout (abort and inform user)
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
try {
|
||||
const response = await callAI({ message, tools })
|
||||
return response
|
||||
} catch (error) {
|
||||
if (error.code === 'rate_limit') {
|
||||
// Retry with exponential backoff
|
||||
await sleep(2000)
|
||||
return await callAI({ message, tools })
|
||||
} else {
|
||||
// Return graceful error
|
||||
return {
|
||||
error: true,
|
||||
message: "I'm having trouble processing that right now. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing AI Agents
|
||||
|
||||
### Unit Tests (Tool Functions)
|
||||
|
||||
Test each tool independently:
|
||||
```typescript
|
||||
describe('search_database tool', () => {
|
||||
it('returns user-specific results', async () => {
|
||||
const result = await search_database({
|
||||
query: 'high priority',
|
||||
table: 'tasks'
|
||||
}, mockContext)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.data.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Integration Tests (Agent Endpoints)
|
||||
|
||||
Test agent responses:
|
||||
```typescript
|
||||
describe('POST /api/agents/assistant', () => {
|
||||
it('responds to simple query', async () => {
|
||||
const res = await app.request('/api/agents/assistant', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer token'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: 'List my tasks'
|
||||
})
|
||||
})
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
const data = await res.json()
|
||||
expect(data.response).toContain('task')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Manual Testing
|
||||
|
||||
**Test Prompts**:
|
||||
- Simple queries: "What tasks do I have?"
|
||||
- Tool usage: "Create a new task called 'Review PR'"
|
||||
- Edge cases: "Delete all my tasks" (should confirm first)
|
||||
- Unclear input: "Do the thing" (should ask for clarification)
|
||||
- Multi-step: "Find high-priority tasks and summarize them"
|
||||
|
||||
---
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
**Metrics to track**:
|
||||
- Agent invocations per day
|
||||
- Average response time
|
||||
- Token usage (input + output)
|
||||
- Tool call frequency
|
||||
- Error rate
|
||||
|
||||
**Logging**:
|
||||
```typescript
|
||||
console.log('[Agent]', {
|
||||
agent: 'assistant',
|
||||
userId: context.get('userId'),
|
||||
message: message.substring(0, 50),
|
||||
tokensUsed: response.usage.total_tokens,
|
||||
toolsCalled: toolCalls.map(t => t.name),
|
||||
responseTime: Date.now() - startTime
|
||||
})
|
||||
```
|
||||
|
||||
**Cloudflare Workers Analytics Engine** (optional):
|
||||
```typescript
|
||||
await env.ANALYTICS.writeDataPoint({
|
||||
indexes: [userId],
|
||||
doubles: [responseTime, tokensUsed],
|
||||
blobs: [agentName, toolsCalled]
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Agent Enhancements
|
||||
|
||||
- [ ] Add [agent name] for [purpose]
|
||||
- [ ] Integrate [tool name] for [capability]
|
||||
- [ ] Implement [workflow name]
|
||||
- [ ] Add voice input/output
|
||||
- [ ] Multi-modal support (images, files)
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial agent configuration
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
438
templates/API_ENDPOINTS.md
Normal file
438
templates/API_ENDPOINTS.md
Normal file
@@ -0,0 +1,438 @@
|
||||
# API Endpoints: [Project Name]
|
||||
|
||||
**Base URL**: `/api`
|
||||
**Framework**: Hono (Cloudflare Workers)
|
||||
**Auth**: Clerk JWT with custom template
|
||||
**Validation**: Zod schemas
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
All API endpoints follow RESTful conventions and return JSON responses.
|
||||
|
||||
**Base URL**:
|
||||
- Local dev: `http://localhost:5173/api`
|
||||
- Production: `https://[your-domain].workers.dev/api`
|
||||
|
||||
**Authentication**: Most endpoints require a valid Clerk JWT in the `Authorization` header:
|
||||
```
|
||||
Authorization: Bearer <jwt_token>
|
||||
```
|
||||
|
||||
**Content Type**: All requests and responses use `application/json`
|
||||
|
||||
---
|
||||
|
||||
## Response Format
|
||||
|
||||
### Success Response
|
||||
```json
|
||||
{
|
||||
"data": { /* response data */ },
|
||||
"meta": { /* optional metadata like pagination */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
```json
|
||||
{
|
||||
"error": "Human-readable error message",
|
||||
"code": "ERROR_CODE",
|
||||
"details": { /* optional additional context */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Standard Error Codes**:
|
||||
- `VALIDATION_ERROR` (400): Request body failed validation
|
||||
- `UNAUTHORIZED` (401): Missing or invalid JWT
|
||||
- `FORBIDDEN` (403): Valid JWT but insufficient permissions
|
||||
- `NOT_FOUND` (404): Resource doesn't exist
|
||||
- `INTERNAL_ERROR` (500): Server error
|
||||
|
||||
---
|
||||
|
||||
## Authentication Endpoints
|
||||
|
||||
### POST `/api/auth/verify`
|
||||
**Purpose**: Verify JWT token validity
|
||||
**Auth**: None (public endpoint)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"valid": true,
|
||||
"email": "user@example.com",
|
||||
"userId": "clerk_user_123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 401**:
|
||||
```json
|
||||
{
|
||||
"error": "Invalid or expired token",
|
||||
"code": "UNAUTHORIZED"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**:
|
||||
```typescript
|
||||
const schema = z.object({
|
||||
token: z.string().min(1)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/auth/me`
|
||||
**Purpose**: Get current authenticated user's profile
|
||||
**Auth**: Required
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"email": "user@example.com",
|
||||
"displayName": "John Doe",
|
||||
"avatarUrl": "https://r2.../avatar.jpg",
|
||||
"createdAt": 1234567890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 401**:
|
||||
```json
|
||||
{
|
||||
"error": "Not authenticated",
|
||||
"code": "UNAUTHORIZED"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [Resource] Endpoints
|
||||
|
||||
### GET `/api/[resource]`
|
||||
**Purpose**: List all [resources] for authenticated user
|
||||
**Auth**: Required
|
||||
|
||||
**Query Parameters**:
|
||||
- `limit` (optional): Number of items to return (default: 50, max: 100)
|
||||
- `offset` (optional): Number of items to skip (default: 0)
|
||||
- `sort` (optional): Sort field (default: `created_at`)
|
||||
- `order` (optional): Sort order - `asc` or `desc` (default: `desc`)
|
||||
|
||||
**Example**: `/api/[resource]?limit=20&offset=0&sort=created_at&order=desc`
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"userId": 1,
|
||||
"[field]": "value",
|
||||
"createdAt": 1234567890,
|
||||
"updatedAt": 1234567890
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"total": 42,
|
||||
"limit": 20,
|
||||
"offset": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 401**: Not authenticated
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/[resource]/:id`
|
||||
**Purpose**: Get a specific [resource] by ID
|
||||
**Auth**: Required
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"userId": 1,
|
||||
"[field]": "value",
|
||||
"createdAt": 1234567890,
|
||||
"updatedAt": 1234567890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 404**:
|
||||
```json
|
||||
{
|
||||
"error": "[Resource] not found",
|
||||
"code": "NOT_FOUND"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 403**: User doesn't own this resource
|
||||
|
||||
---
|
||||
|
||||
### POST `/api/[resource]`
|
||||
**Purpose**: Create a new [resource]
|
||||
**Auth**: Required
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"[field1]": "value",
|
||||
"[field2]": "value"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**:
|
||||
```typescript
|
||||
const schema = z.object({
|
||||
[field1]: z.string().min(1).max(100),
|
||||
[field2]: z.string().optional(),
|
||||
// ... other fields
|
||||
})
|
||||
```
|
||||
|
||||
**Response 201**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 42,
|
||||
"userId": 1,
|
||||
"[field1]": "value",
|
||||
"[field2]": "value",
|
||||
"createdAt": 1234567890,
|
||||
"updatedAt": 1234567890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 400** (validation error):
|
||||
```json
|
||||
{
|
||||
"error": "Validation failed",
|
||||
"code": "VALIDATION_ERROR",
|
||||
"details": {
|
||||
"field1": "Must be at least 1 character"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PATCH `/api/[resource]/:id`
|
||||
**Purpose**: Update an existing [resource]
|
||||
**Auth**: Required
|
||||
|
||||
**Request Body** (all fields optional):
|
||||
```json
|
||||
{
|
||||
"[field1]": "new value",
|
||||
"[field2]": "new value"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**:
|
||||
```typescript
|
||||
const schema = z.object({
|
||||
[field1]: z.string().min(1).max(100).optional(),
|
||||
[field2]: z.string().optional(),
|
||||
}).refine(data => Object.keys(data).length > 0, {
|
||||
message: "At least one field must be provided"
|
||||
})
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 42,
|
||||
"userId": 1,
|
||||
"[field1]": "new value",
|
||||
"[field2]": "new value",
|
||||
"createdAt": 1234567890,
|
||||
"updatedAt": 1234567999
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 404**: Resource not found
|
||||
**Response 403**: User doesn't own this resource
|
||||
|
||||
---
|
||||
|
||||
### DELETE `/api/[resource]/:id`
|
||||
**Purpose**: Delete a [resource]
|
||||
**Auth**: Required
|
||||
|
||||
**Response 204**: No content (success)
|
||||
|
||||
**Response 404**: Resource not found
|
||||
**Response 403**: User doesn't own this resource
|
||||
|
||||
---
|
||||
|
||||
## Middleware
|
||||
|
||||
### CORS Middleware
|
||||
**Applies to**: All routes
|
||||
**Headers**:
|
||||
```
|
||||
Access-Control-Allow-Origin: https://[your-domain].com
|
||||
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
|
||||
Access-Control-Allow-Headers: Content-Type, Authorization
|
||||
Access-Control-Max-Age: 86400
|
||||
```
|
||||
|
||||
### Auth Middleware
|
||||
**Applies to**: All routes except public endpoints
|
||||
**Validates**: Clerk JWT in Authorization header
|
||||
**Adds to context**: `c.get('userId')`, `c.get('email')`
|
||||
**Rejects**: Missing, invalid, or expired tokens (401)
|
||||
|
||||
### Error Handler Middleware
|
||||
**Applies to**: All routes
|
||||
**Catches**: Unhandled errors
|
||||
**Returns**: 500 with sanitized error message
|
||||
**Logs**: Full error details for debugging
|
||||
|
||||
### Validation Middleware
|
||||
**Applies to**: POST and PATCH routes
|
||||
**Validates**: Request body against Zod schema
|
||||
**Returns**: 400 with field-specific errors if validation fails
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
[Optional: Define rate limits if applicable]
|
||||
|
||||
**Default Limits**:
|
||||
- Anonymous requests: 10 requests per minute
|
||||
- Authenticated requests: 100 requests per minute
|
||||
|
||||
**Headers**:
|
||||
```
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 95
|
||||
X-RateLimit-Reset: 1234567890
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhook Endpoints
|
||||
|
||||
[Optional: Document webhooks if applicable]
|
||||
|
||||
### POST `/api/webhooks/[service]`
|
||||
**Purpose**: Handle webhook from [third-party service]
|
||||
**Auth**: Webhook signature verification
|
||||
**Source**: [Service name]
|
||||
|
||||
**Expected Payload**:
|
||||
```json
|
||||
{
|
||||
"event": "event_type",
|
||||
"data": { /* event data */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200**: Webhook processed successfully
|
||||
**Response 400**: Invalid signature or payload
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Testing with curl
|
||||
|
||||
**Create resource**:
|
||||
```bash
|
||||
curl -X POST http://localhost:5173/api/[resource] \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-d '{"field1": "value"}'
|
||||
```
|
||||
|
||||
**Get resource**:
|
||||
```bash
|
||||
curl http://localhost:5173/api/[resource]/1 \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
**Update resource**:
|
||||
```bash
|
||||
curl -X PATCH http://localhost:5173/api/[resource]/1 \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-d '{"field1": "new value"}'
|
||||
```
|
||||
|
||||
**Delete resource**:
|
||||
```bash
|
||||
curl -X DELETE http://localhost:5173/api/[resource]/1 \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
### Getting a Test JWT
|
||||
|
||||
Use Clerk's development mode or sign in to get a token:
|
||||
```javascript
|
||||
// In browser console after logging in
|
||||
const token = await window.Clerk.session.getToken();
|
||||
console.log(token);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
**Wrangler Configuration**: Ensure `wrangler.jsonc` includes necessary bindings:
|
||||
```jsonc
|
||||
{
|
||||
"vars": {
|
||||
"CLERK_PUBLISHABLE_KEY": "pk_test_..."
|
||||
},
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "DB",
|
||||
"database_name": "[your-database]",
|
||||
"database_id": "[database-id]"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Environment Variables** (set via dashboard or CLI):
|
||||
- `CLERK_SECRET_KEY`: Secret key for JWT verification
|
||||
|
||||
---
|
||||
|
||||
## Future Endpoints
|
||||
|
||||
Planned endpoints to implement:
|
||||
- [ ] `/api/[feature]` - [Description]
|
||||
- [ ] `/api/[other-feature]` - [Description]
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial API design
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
495
templates/ARCHITECTURE.md
Normal file
495
templates/ARCHITECTURE.md
Normal file
@@ -0,0 +1,495 @@
|
||||
# Architecture: [Project Name]
|
||||
|
||||
**Deployment Platform**: Cloudflare Workers
|
||||
**Frontend**: Vite + React + Tailwind v4 + shadcn/ui
|
||||
**Backend**: Hono (API routes on same Worker)
|
||||
**Database**: Cloudflare D1 (SQLite)
|
||||
**Storage**: Cloudflare R2 (object storage)
|
||||
**Auth**: Clerk (JWT-based)
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## System Overview
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Browser │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ React App (Vite build) │ │
|
||||
│ │ - Components (shadcn/ui + custom) │ │
|
||||
│ │ - State (TanStack Query + Zustand) │ │
|
||||
│ │ - Forms (React Hook Form + Zod) │ │
|
||||
│ └───────────┬────────────────────────────────────────────┘ │
|
||||
└──────────────┼───────────────────────────────────────────────┘
|
||||
│ HTTPS
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Cloudflare Worker (Edge Runtime) │
|
||||
│ ┌────────────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ Static Assets │ │ API Routes (Hono) │ │
|
||||
│ │ (Vite build output) │ │ /api/* │ │
|
||||
│ │ Served directly │ │ │ │
|
||||
│ │ │ │ Middleware: │ │
|
||||
│ │ / → index.html │ │ - CORS │ │
|
||||
│ │ /assets/* │ │ - Auth (JWT verify) │ │
|
||||
│ │ │ │ - Error handling │ │
|
||||
│ └────────────────────────┘ │ - Validation (Zod) │ │
|
||||
│ └───────┬──────────────────┘ │
|
||||
│ │ │
|
||||
└────────────────────────────────────────┼─────────────────────┘
|
||||
│
|
||||
┌──────────────────────────────┼──────────────────────────┐
|
||||
│ │ │
|
||||
↓ ↓ ↓
|
||||
┌───────────────────┐ ┌───────────────────┐ ┌──────────────────┐
|
||||
│ Cloudflare D1 │ │ Cloudflare R2 │ │ Clerk Auth │
|
||||
│ (Database) │ │ (File Storage) │ │ (External) │
|
||||
│ │ │ │ │ │
|
||||
│ - users │ │ - avatars/ │ │ - User accounts │
|
||||
│ - [other tables] │ │ - uploads/ │ │ - JWT tokens │
|
||||
│ │ │ │ │ - Social auth │
|
||||
└───────────────────┘ └───────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components Breakdown
|
||||
|
||||
### Frontend (Browser)
|
||||
|
||||
**Technology**: React 19 + Vite + Tailwind v4
|
||||
|
||||
**Responsibilities**:
|
||||
- Render UI components
|
||||
- Handle user interactions
|
||||
- Client-side validation (Zod)
|
||||
- Optimistic updates
|
||||
- State management
|
||||
- Routing (React Router or TanStack Router)
|
||||
|
||||
**Key Libraries**:
|
||||
- `@clerk/clerk-react` - Authentication UI and hooks
|
||||
- `@tanstack/react-query` - Server state management (caching, refetching)
|
||||
- `zustand` - Client state management (UI state, preferences)
|
||||
- `react-hook-form` - Form state and validation
|
||||
- `zod` - Schema validation
|
||||
- `shadcn/ui` - UI component library (Radix UI primitives)
|
||||
|
||||
**State Architecture**:
|
||||
```
|
||||
Server State (TanStack Query)
|
||||
↓
|
||||
Cached API responses, auto-refetch, background sync
|
||||
Examples: user data, tasks, analytics
|
||||
|
||||
Client State (Zustand)
|
||||
↓
|
||||
UI state, form state, user preferences
|
||||
Examples: sidebar open/closed, theme, filters
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Backend (Cloudflare Worker)
|
||||
|
||||
**Technology**: Hono web framework on Cloudflare Workers
|
||||
|
||||
**Responsibilities**:
|
||||
- Serve static assets (Vite build)
|
||||
- API request routing
|
||||
- Authentication/authorization
|
||||
- Server-side validation
|
||||
- Business logic
|
||||
- Database operations
|
||||
- Third-party API integration
|
||||
|
||||
**Route Structure**:
|
||||
```typescript
|
||||
app.use('*', cors())
|
||||
app.use('/api/*', authMiddleware)
|
||||
|
||||
// Static assets
|
||||
app.get('/', serveStatic({ path: './dist/index.html' }))
|
||||
app.get('/assets/*', serveStatic({ root: './dist' }))
|
||||
|
||||
// API routes
|
||||
app.route('/api/auth', authRoutes)
|
||||
app.route('/api/[resource]', [resource]Routes)
|
||||
// ... more routes
|
||||
|
||||
app.onError(errorHandler)
|
||||
```
|
||||
|
||||
**Middleware Pipeline**:
|
||||
```
|
||||
Request
|
||||
↓
|
||||
CORS Middleware (allow frontend origin)
|
||||
↓
|
||||
Auth Middleware (verify JWT for /api/* routes)
|
||||
↓
|
||||
Route Handler
|
||||
↓
|
||||
Validation Middleware (Zod schema)
|
||||
↓
|
||||
Business Logic
|
||||
↓
|
||||
Response
|
||||
↓
|
||||
Error Handler (catch unhandled errors)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Database (Cloudflare D1)
|
||||
|
||||
**Technology**: SQLite (via D1)
|
||||
|
||||
**Access Pattern**: SQL queries via Worker bindings
|
||||
|
||||
**Schema**: See `DATABASE_SCHEMA.md` for full details
|
||||
|
||||
**Usage**:
|
||||
```typescript
|
||||
// In Worker
|
||||
const result = await c.env.DB.prepare(
|
||||
'SELECT * FROM users WHERE id = ?'
|
||||
).bind(userId).first()
|
||||
```
|
||||
|
||||
**Migrations**: Manual SQL files in `migrations/`
|
||||
|
||||
**Backups**: Export via `npx wrangler d1 export`
|
||||
|
||||
---
|
||||
|
||||
### Storage (Cloudflare R2)
|
||||
|
||||
**Technology**: S3-compatible object storage
|
||||
|
||||
**Use Cases**:
|
||||
- User avatars
|
||||
- File uploads
|
||||
- Generated assets
|
||||
|
||||
**Access Pattern**: Direct upload from Worker, signed URLs for browser access
|
||||
|
||||
**Bucket Structure**:
|
||||
```
|
||||
[bucket-name]/
|
||||
├── avatars/
|
||||
│ ├── user-1.jpg
|
||||
│ └── user-2.png
|
||||
├── uploads/
|
||||
│ └── [user-id]/
|
||||
│ └── document.pdf
|
||||
└── generated/
|
||||
└── export-123.csv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Authentication (Clerk)
|
||||
|
||||
**Technology**: Clerk (external SaaS)
|
||||
|
||||
**Flow**:
|
||||
```
|
||||
1. User clicks "Sign In"
|
||||
2. Clerk modal opens (handled by @clerk/clerk-react)
|
||||
3. User authenticates (email/password or social)
|
||||
4. Clerk returns JWT
|
||||
5. Frontend includes JWT in API requests (Authorization: Bearer ...)
|
||||
6. Worker verifies JWT using Clerk secret key
|
||||
7. Worker extracts user email/ID from JWT
|
||||
8. Worker processes request with user context
|
||||
```
|
||||
|
||||
**JWT Custom Template** (configured in Clerk):
|
||||
```json
|
||||
{
|
||||
"email": "{{user.primary_email_address}}",
|
||||
"userId": "{{user.id}}",
|
||||
"metadata": {
|
||||
"displayName": "{{user.first_name}} {{user.last_name}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Verification** (in Worker):
|
||||
```typescript
|
||||
import { verifyToken } from '@clerk/backend'
|
||||
|
||||
const token = c.req.header('Authorization')?.replace('Bearer ', '')
|
||||
const verified = await verifyToken(token, {
|
||||
secretKey: c.env.CLERK_SECRET_KEY
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Patterns
|
||||
|
||||
### User Authentication Flow
|
||||
|
||||
```
|
||||
1. User loads app
|
||||
→ React app served from Worker static assets
|
||||
→ ClerkProvider wraps app
|
||||
|
||||
2. User not authenticated
|
||||
→ Show sign-in button
|
||||
→ User clicks sign-in
|
||||
→ Clerk modal opens
|
||||
|
||||
3. User signs in
|
||||
→ Clerk handles authentication
|
||||
→ Returns JWT to browser
|
||||
→ React stores JWT in memory (via ClerkProvider)
|
||||
|
||||
4. User makes API request
|
||||
→ React includes JWT in Authorization header
|
||||
→ Worker middleware verifies JWT
|
||||
→ Extracts user info from JWT
|
||||
→ Passes to route handler via context
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CRUD Operation Flow (Example: Create Task)
|
||||
|
||||
```
|
||||
1. User fills out form
|
||||
↓
|
||||
2. React Hook Form validates locally (Zod schema)
|
||||
↓
|
||||
3. Validation passes → Submit to API
|
||||
↓
|
||||
4. POST /api/tasks with JWT in header
|
||||
↓
|
||||
5. Worker receives request
|
||||
↓
|
||||
6. CORS middleware allows request
|
||||
↓
|
||||
7. Auth middleware verifies JWT → extracts userId
|
||||
↓
|
||||
8. Route handler receives request
|
||||
↓
|
||||
9. Validation middleware validates body (Zod schema)
|
||||
↓
|
||||
10. Business logic creates task in D1
|
||||
INSERT INTO tasks (user_id, title, ...) VALUES (?, ?, ...)
|
||||
↓
|
||||
11. Return created task (201)
|
||||
↓
|
||||
12. TanStack Query updates cache
|
||||
↓
|
||||
13. React re-renders with new task
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### File Upload Flow
|
||||
|
||||
```
|
||||
1. User selects file in browser
|
||||
↓
|
||||
2. React sends file to POST /api/upload
|
||||
↓
|
||||
3. Worker receives file (multipart/form-data)
|
||||
↓
|
||||
4. Worker validates file (size, type)
|
||||
↓
|
||||
5. Worker uploads to R2
|
||||
await c.env.R2_BUCKET.put(`uploads/${userId}/${filename}`, file)
|
||||
↓
|
||||
6. Worker creates DB record with R2 key
|
||||
INSERT INTO files (user_id, r2_key, filename, ...) VALUES (...)
|
||||
↓
|
||||
7. Return file metadata (200)
|
||||
↓
|
||||
8. Frontend shows uploaded file with signed URL
|
||||
GET /api/files/:id/url → Worker generates R2 signed URL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
### Development Environment
|
||||
|
||||
```
|
||||
localhost:5173
|
||||
↓
|
||||
Vite dev server (HMR)
|
||||
↓
|
||||
@cloudflare/vite-plugin runs Worker alongside Vite
|
||||
↓
|
||||
Worker connects to local D1 database
|
||||
↓
|
||||
All requests proxied correctly (frontend ↔ API on same port)
|
||||
```
|
||||
|
||||
**Start dev**:
|
||||
```bash
|
||||
npm run dev
|
||||
# Vite serves frontend on :5173
|
||||
# Worker API available at :5173/api
|
||||
# Uses local D1 and R2 buckets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Production Environment
|
||||
|
||||
```
|
||||
https://[app-name].[account].workers.dev (or custom domain)
|
||||
↓
|
||||
Cloudflare Worker (edge locations globally)
|
||||
↓
|
||||
Static assets cached at edge
|
||||
API requests hit Worker
|
||||
↓
|
||||
D1 database (regional, auto-replicated)
|
||||
R2 storage (global, low latency)
|
||||
```
|
||||
|
||||
**Deploy**:
|
||||
```bash
|
||||
npm run build # Vite builds frontend → dist/
|
||||
npx wrangler deploy # Uploads Worker + assets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication
|
||||
- **JWT verification** on all protected routes
|
||||
- **Clerk handles** password hashing, session management, social auth
|
||||
- **No passwords stored** in our database
|
||||
|
||||
### Authorization
|
||||
- **User ownership checks** before mutations
|
||||
- Example: Can't delete another user's task
|
||||
```typescript
|
||||
const task = await getTask(id)
|
||||
if (task.user_id !== c.get('userId')) {
|
||||
return c.json({ error: 'Forbidden' }, 403)
|
||||
}
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
- **Client-side** (Zod): Fast feedback, better UX
|
||||
- **Server-side** (Zod): Security, trust boundary
|
||||
- **Same schemas** used on both sides
|
||||
|
||||
### CORS
|
||||
- **Restrict origins** to production domain
|
||||
- **Allow credentials** for JWT cookies (if used)
|
||||
|
||||
### Secrets Management
|
||||
- **Environment variables** for API keys
|
||||
- **Never committed** to git
|
||||
- **Wrangler secrets** for production
|
||||
|
||||
### Rate Limiting
|
||||
[Optional: Add if implemented]
|
||||
- X requests per minute per IP
|
||||
- Higher limits for authenticated users
|
||||
|
||||
---
|
||||
|
||||
## Scaling Considerations
|
||||
|
||||
### Current Architecture Scales to:
|
||||
- **Requests**: Millions/day (Cloudflare Workers auto-scale)
|
||||
- **Users**: 10k-100k (D1 suitable for this range)
|
||||
- **Data**: Moderate (D1 max 10GB per database)
|
||||
|
||||
### If You Need to Scale Beyond:
|
||||
- **Database**: Consider Hyperdrive + external Postgres for >100k users
|
||||
- **Storage**: R2 scales infinitely
|
||||
- **Real-time**: Add Durable Objects for WebSocket connections
|
||||
- **Compute**: Workers already global and auto-scaling
|
||||
|
||||
---
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
[Optional: Define monitoring strategy]
|
||||
|
||||
**Metrics to Track**:
|
||||
- Request volume and latency
|
||||
- Error rates (4xx, 5xx)
|
||||
- Database query performance
|
||||
- R2 upload/download volumes
|
||||
|
||||
**Tools**:
|
||||
- Cloudflare Analytics (built-in)
|
||||
- Workers Analytics Engine (custom metrics)
|
||||
- Sentry or similar for error tracking
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Local development**: `npm run dev`
|
||||
2. **Make changes**: Edit code, hot reload
|
||||
3. **Test locally**: Manual testing or automated tests
|
||||
4. **Commit**: `git commit -m "feat: add feature"`
|
||||
5. **Deploy**: `npx wrangler deploy`
|
||||
6. **Monitor**: Check Cloudflare dashboard for errors
|
||||
|
||||
---
|
||||
|
||||
## Technology Choices Rationale
|
||||
|
||||
**Why Cloudflare Workers?**
|
||||
- Global edge deployment (low latency)
|
||||
- Auto-scaling (no server management)
|
||||
- Integrated services (D1, R2, KV)
|
||||
- Cost-effective for moderate traffic
|
||||
|
||||
**Why Vite?**
|
||||
- Fast dev server with HMR
|
||||
- Excellent React support
|
||||
- Simple config
|
||||
- `@cloudflare/vite-plugin` integrates perfectly with Workers
|
||||
|
||||
**Why Hono?**
|
||||
- Lightweight and fast
|
||||
- Express-like API (familiar)
|
||||
- Native TypeScript support
|
||||
- Works on any runtime (including Workers)
|
||||
|
||||
**Why Clerk?**
|
||||
- Handles complex auth flows
|
||||
- Social auth out of the box
|
||||
- Great DX
|
||||
- No need to build/maintain auth system
|
||||
|
||||
**Why Tailwind v4?**
|
||||
- Utility-first CSS
|
||||
- shadcn/ui compatibility
|
||||
- Dark mode support
|
||||
- v4 Vite plugin (no PostCSS needed)
|
||||
|
||||
---
|
||||
|
||||
## Future Architecture Enhancements
|
||||
|
||||
Potential additions:
|
||||
- [ ] Durable Objects for real-time features (WebSockets)
|
||||
- [ ] Workers AI for AI-powered features
|
||||
- [ ] Queues for background jobs
|
||||
- [ ] Hyperdrive for external database connections
|
||||
- [ ] Vectorize for semantic search
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial architecture design
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
269
templates/DATABASE_SCHEMA.md
Normal file
269
templates/DATABASE_SCHEMA.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# Database Schema: [Project Name]
|
||||
|
||||
**Database**: Cloudflare D1 (SQLite)
|
||||
**Migrations**: `migrations/` directory
|
||||
**ORM**: [Drizzle ORM / Raw SQL / None]
|
||||
**Schema Version**: 1.0
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the complete database schema including tables, relationships, indexes, and migrations.
|
||||
|
||||
**Design Principles**:
|
||||
- Normalize data to reduce redundancy
|
||||
- Index frequently queried columns
|
||||
- Use foreign keys for referential integrity
|
||||
- Include timestamps for audit trail
|
||||
- Use INTEGER for IDs (SQLite auto-increment)
|
||||
- Use TEXT for strings (SQLite doesn't enforce varchar limits)
|
||||
- Use INTEGER for booleans (0 = false, 1 = true)
|
||||
- Use INTEGER for timestamps (Unix epoch)
|
||||
|
||||
---
|
||||
|
||||
## Tables
|
||||
|
||||
### `users`
|
||||
**Purpose**: User accounts and authentication data
|
||||
|
||||
| Column | Type | Constraints | Default | Notes |
|
||||
|--------|------|-------------|---------|-------|
|
||||
| `id` | INTEGER | PRIMARY KEY | AUTO | Auto-increment |
|
||||
| `email` | TEXT | UNIQUE, NOT NULL | - | Login identifier |
|
||||
| `clerk_id` | TEXT | UNIQUE, NOT NULL | - | Clerk user ID |
|
||||
| `display_name` | TEXT | NULL | - | User's display name |
|
||||
| `avatar_url` | TEXT | NULL | - | Profile picture URL (R2) |
|
||||
| `created_at` | INTEGER | NOT NULL | - | Unix timestamp |
|
||||
| `updated_at` | INTEGER | NOT NULL | - | Unix timestamp |
|
||||
|
||||
**Indexes**:
|
||||
- `idx_users_email` on `email` (for login lookups)
|
||||
- `idx_users_clerk_id` on `clerk_id` (for auth verification)
|
||||
|
||||
**Relationships**:
|
||||
- One-to-many with `[related_table]`
|
||||
|
||||
**Notes**:
|
||||
- `clerk_id` comes from Clerk authentication
|
||||
- `avatar_url` points to R2 storage if user uploads custom avatar
|
||||
- Emails are unique and used for account identification
|
||||
|
||||
---
|
||||
|
||||
### `[table_name]`
|
||||
**Purpose**: [Description of what this table stores]
|
||||
|
||||
| Column | Type | Constraints | Default | Notes |
|
||||
|--------|------|-------------|---------|-------|
|
||||
| `id` | INTEGER | PRIMARY KEY | AUTO | Auto-increment |
|
||||
| `user_id` | INTEGER | FOREIGN KEY, NOT NULL | - | References `users(id)` |
|
||||
| `[field]` | [TYPE] | [CONSTRAINTS] | [DEFAULT] | [Notes] |
|
||||
| `created_at` | INTEGER | NOT NULL | - | Unix timestamp |
|
||||
| `updated_at` | INTEGER | NOT NULL | - | Unix timestamp |
|
||||
|
||||
**Indexes**:
|
||||
- `idx_[table]_user_id` on `user_id` (for user-specific queries)
|
||||
- `idx_[table]_[field]` on `[field]` (if frequently queried)
|
||||
|
||||
**Relationships**:
|
||||
- Many-to-one with `users`
|
||||
- [Other relationships]
|
||||
|
||||
**Notes**:
|
||||
- [Important details about this table]
|
||||
|
||||
---
|
||||
|
||||
### `[junction_table]` (for many-to-many relationships)
|
||||
**Purpose**: Links [table_a] and [table_b] in many-to-many relationship
|
||||
|
||||
| Column | Type | Constraints | Default | Notes |
|
||||
|--------|------|-------------|---------|-------|
|
||||
| `id` | INTEGER | PRIMARY KEY | AUTO | Auto-increment |
|
||||
| `[table_a]_id` | INTEGER | FOREIGN KEY, NOT NULL | - | References `[table_a](id)` |
|
||||
| `[table_b]_id` | INTEGER | FOREIGN KEY, NOT NULL | - | References `[table_b](id)` |
|
||||
| `created_at` | INTEGER | NOT NULL | - | Unix timestamp |
|
||||
|
||||
**Indexes**:
|
||||
- `idx_[junction]_[table_a]_id` on `[table_a]_id`
|
||||
- `idx_[junction]_[table_b]_id` on `[table_b]_id`
|
||||
- `idx_[junction]_composite` on `([table_a]_id, [table_b]_id)` UNIQUE
|
||||
|
||||
**Relationships**:
|
||||
- Many-to-one with `[table_a]`
|
||||
- Many-to-one with `[table_b]`
|
||||
|
||||
**Notes**:
|
||||
- Composite unique index prevents duplicate associations
|
||||
|
||||
---
|
||||
|
||||
## Relationships Diagram
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ users │
|
||||
└──────┬──────┘
|
||||
│ 1
|
||||
│
|
||||
│ N
|
||||
┌──────┴──────────┐
|
||||
│ [child_table] │
|
||||
└─────────────────┘
|
||||
|
||||
[Add more relationships as needed]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migrations
|
||||
|
||||
### Migration 0001: Initial Schema
|
||||
**File**: `migrations/0001_initial_schema.sql`
|
||||
**Created**: [Date]
|
||||
**Purpose**: Create initial tables for [core entities]
|
||||
|
||||
**Creates**:
|
||||
- `users` table
|
||||
- `[other_tables]` tables
|
||||
|
||||
**Indexes**:
|
||||
- All primary indexes for frequently queried columns
|
||||
|
||||
**Run**:
|
||||
```bash
|
||||
npx wrangler d1 execute [DB_NAME] --local --file=migrations/0001_initial_schema.sql
|
||||
npx wrangler d1 execute [DB_NAME] --remote --file=migrations/0001_initial_schema.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Migration 0002: [Description]
|
||||
**File**: `migrations/0002_[name].sql`
|
||||
**Created**: [Date]
|
||||
**Purpose**: [What this migration does]
|
||||
|
||||
**Changes**:
|
||||
- Add `[column]` to `[table]`
|
||||
- Create `[new_table]` table
|
||||
- Add index `[index_name]`
|
||||
|
||||
**Run**:
|
||||
```bash
|
||||
npx wrangler d1 execute [DB_NAME] --local --file=migrations/0002_[name].sql
|
||||
npx wrangler d1 execute [DB_NAME] --remote --file=migrations/0002_[name].sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Seed Data
|
||||
|
||||
For development and testing, seed the database with sample data.
|
||||
|
||||
**File**: `migrations/seed.sql` (run manually, not in production)
|
||||
|
||||
**Sample Data**:
|
||||
- 3-5 test users
|
||||
- [Other sample data relevant to testing]
|
||||
|
||||
**Run**:
|
||||
```bash
|
||||
npx wrangler d1 execute [DB_NAME] --local --file=migrations/seed.sql
|
||||
```
|
||||
|
||||
**Sample Users**:
|
||||
```sql
|
||||
INSERT INTO users (email, clerk_id, display_name, created_at, updated_at)
|
||||
VALUES
|
||||
('test1@example.com', 'clerk_test_1', 'Test User 1', strftime('%s', 'now'), strftime('%s', 'now')),
|
||||
('test2@example.com', 'clerk_test_2', 'Test User 2', strftime('%s', 'now'), strftime('%s', 'now')),
|
||||
('test3@example.com', 'clerk_test_3', 'Test User 3', strftime('%s', 'now'), strftime('%s', 'now'));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Query Patterns
|
||||
|
||||
### Common Queries
|
||||
|
||||
**Get user by email**:
|
||||
```sql
|
||||
SELECT * FROM users WHERE email = ?;
|
||||
```
|
||||
|
||||
**Get all [items] for a user**:
|
||||
```sql
|
||||
SELECT * FROM [table] WHERE user_id = ? ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
**Get [item] with related data**:
|
||||
```sql
|
||||
SELECT
|
||||
[table].*,
|
||||
users.display_name,
|
||||
users.avatar_url
|
||||
FROM [table]
|
||||
JOIN users ON [table].user_id = users.id
|
||||
WHERE [table].id = ?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constraints and Validation
|
||||
|
||||
### Enforced at Database Level
|
||||
- Primary keys (unique, not null)
|
||||
- Foreign keys (referential integrity)
|
||||
- Unique constraints (email, composite indexes)
|
||||
- Not null constraints (required fields)
|
||||
|
||||
### Enforced at Application Level (Zod)
|
||||
- Email format validation
|
||||
- String length limits
|
||||
- Enum values
|
||||
- Complex business logic
|
||||
|
||||
**Why split?**: Database enforces data integrity, application provides user-friendly error messages.
|
||||
|
||||
---
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
### Export Database
|
||||
```bash
|
||||
npx wrangler d1 export [DB_NAME] --local --output=backup.sql
|
||||
npx wrangler d1 export [DB_NAME] --remote --output=backup.sql
|
||||
```
|
||||
|
||||
### Import Database
|
||||
```bash
|
||||
npx wrangler d1 execute [DB_NAME] --local --file=backup.sql
|
||||
npx wrangler d1 execute [DB_NAME] --remote --file=backup.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
**Indexes**: All frequently queried columns are indexed
|
||||
**Query Optimization**: Use `EXPLAIN QUERY PLAN` to check query performance
|
||||
**Pagination**: Use `LIMIT` and `OFFSET` for large result sets
|
||||
**Caching**: Consider Workers KV for frequently accessed, rarely changed data
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential schema changes to consider:
|
||||
- [ ] [Feature requiring schema change]
|
||||
- [ ] [Another potential enhancement]
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial schema design
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
229
templates/IMPLEMENTATION_PHASES.md
Normal file
229
templates/IMPLEMENTATION_PHASES.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Implementation Phases: [Project Name]
|
||||
|
||||
**Project Type**: [Web App / Dashboard / API / Tool / etc]
|
||||
**Stack**: Cloudflare Workers + Vite + React + Tailwind v4 + shadcn/ui + D1
|
||||
**Estimated Total**: [X hours] (~[Y minutes] human time with AI assistance)
|
||||
**Created**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document breaks down the project into context-safe phases. Each phase:
|
||||
- Can be completed in one 2-4 hour session
|
||||
- Includes built-in verification criteria
|
||||
- Has clear exit criteria
|
||||
- Touches 5-8 files maximum
|
||||
- Requires minimal context from other phases
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: [Name - Usually "Project Setup"]
|
||||
**Type**: Infrastructure
|
||||
**Estimated**: 2-3 hours (~2-3 minutes human time)
|
||||
**Files**: `package.json`, `wrangler.jsonc`, `vite.config.ts`, `src/index.ts`, `src/index.css`
|
||||
|
||||
### Tasks
|
||||
- [ ] Scaffold Cloudflare Worker with Vite
|
||||
- [ ] Install dependencies (React, Tailwind v4, shadcn/ui, etc)
|
||||
- [ ] Configure `wrangler.jsonc` with bindings
|
||||
- [ ] Setup Tailwind v4 with `@tailwindcss/vite` plugin
|
||||
- [ ] Initialize shadcn/ui with dark mode support
|
||||
- [ ] Create basic "Hello World" component
|
||||
- [ ] Test local dev server
|
||||
- [ ] Test deployment to Cloudflare
|
||||
|
||||
### Verification Criteria
|
||||
- [ ] `npm run dev` starts without errors
|
||||
- [ ] `localhost:5173` shows React app with Tailwind styling
|
||||
- [ ] Dark/light mode toggle works
|
||||
- [ ] `npm run build` succeeds
|
||||
- [ ] `npx wrangler deploy` deploys successfully
|
||||
- [ ] Deployed URL shows working app
|
||||
|
||||
### Exit Criteria
|
||||
Working development environment with successful test deployment. Can iterate on code locally and deploy to Cloudflare.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: [Name - Usually "Database Schema" or next major component]
|
||||
**Type**: [Database/API/UI/Integration]
|
||||
**Estimated**: [X hours]
|
||||
**Files**: [List specific files]
|
||||
|
||||
### File Map
|
||||
[Optional but recommended for API/UI/Integration phases]
|
||||
|
||||
- `src/[file-name].ts` (estimated ~XXX lines)
|
||||
- **Purpose**: [What this file does]
|
||||
- **Key exports**: [Main functions/components/types]
|
||||
- **Dependencies**: [What it imports]
|
||||
- **Used by**: [What uses it]
|
||||
|
||||
- `src/[another-file].ts` (modify existing)
|
||||
- **Purpose**: [What this file does]
|
||||
- **Modifications**: [What changes to make]
|
||||
|
||||
### Data Flow
|
||||
[Optional - Use Mermaid diagram for complex interactions]
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Component A
|
||||
participant B as Component B
|
||||
A->>B: Action
|
||||
B->>A: Response
|
||||
```
|
||||
|
||||
### Critical Dependencies
|
||||
|
||||
**Internal**: [List codebase files this phase depends on]
|
||||
**External**: [List npm packages needed]
|
||||
**Configuration**: [Environment variables, config files]
|
||||
**Bindings**: [Cloudflare bindings needed: D1, R2, KV, etc]
|
||||
|
||||
### Gotchas & Known Issues
|
||||
[Optional but valuable - Document non-obvious behavior]
|
||||
|
||||
- **[Issue name]**: [Description and pattern/solution]
|
||||
- **[Another issue]**: [Description and pattern/solution]
|
||||
|
||||
### Tasks
|
||||
- [ ] [Specific task 1]
|
||||
- [ ] [Specific task 2]
|
||||
- [ ] [Specific task 3]
|
||||
- [ ] Test basic functionality
|
||||
|
||||
### Verification Criteria
|
||||
- [ ] [Specific test 1]
|
||||
- [ ] [Specific test 2]
|
||||
- [ ] [Specific test 3]
|
||||
|
||||
### Exit Criteria
|
||||
[Clear definition of when this phase is complete]
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: [Name]
|
||||
**Type**: [Database/API/UI/Integration]
|
||||
**Estimated**: [X hours]
|
||||
**Files**: [List specific files]
|
||||
|
||||
### File Map
|
||||
[Include for API/UI/Integration phases]
|
||||
|
||||
### Data Flow
|
||||
[Include Mermaid diagram if helpful]
|
||||
|
||||
### Critical Dependencies
|
||||
**Internal**: | **External**: | **Configuration**: | **Bindings**:
|
||||
|
||||
### Gotchas & Known Issues
|
||||
[Document non-obvious behavior]
|
||||
|
||||
### Tasks
|
||||
- [ ] [Tasks...]
|
||||
|
||||
### Verification Criteria
|
||||
- [ ] [Tests...]
|
||||
|
||||
### Exit Criteria
|
||||
[Definition of done]
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: [Name]
|
||||
**Type**: [Database/API/UI/Integration]
|
||||
**Estimated**: [X hours]
|
||||
**Files**: [List specific files]
|
||||
|
||||
### File Map
|
||||
[Include for API/UI/Integration phases]
|
||||
|
||||
### Data Flow
|
||||
[Include Mermaid diagram if helpful]
|
||||
|
||||
### Critical Dependencies
|
||||
**Internal**: | **External**: | **Configuration**: | **Bindings**:
|
||||
|
||||
### Gotchas & Known Issues
|
||||
[Document non-obvious behavior]
|
||||
|
||||
### Tasks
|
||||
- [ ] [Tasks...]
|
||||
|
||||
### Verification Criteria
|
||||
- [ ] [Tests...]
|
||||
|
||||
### Exit Criteria
|
||||
[Definition of done]
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: [Name - Optional: Testing/Polish/Deployment]
|
||||
**Type**: [Testing/Integration]
|
||||
**Estimated**: [X hours]
|
||||
**Files**: [List specific files]
|
||||
|
||||
### Tasks
|
||||
- [ ] [Tasks...]
|
||||
|
||||
### Verification Criteria
|
||||
- [ ] [Tests...]
|
||||
|
||||
### Exit Criteria
|
||||
[Definition of done]
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
### Testing Strategy
|
||||
[Describe when/how testing happens]
|
||||
- Option A: Testing built into each phase (verification criteria)
|
||||
- Option B: Separate testing phase at end
|
||||
- Option C: Hybrid - smoke tests per phase, comprehensive tests at end
|
||||
|
||||
### Deployment Strategy
|
||||
[Describe deployment approach]
|
||||
- Option A: Deploy after each phase (continuous deployment)
|
||||
- Option B: Deploy at milestones (after groups of phases)
|
||||
- Option C: Deploy at end (single production push)
|
||||
|
||||
### Context Management
|
||||
Phases are sized to fit within a single session including:
|
||||
- Implementation time
|
||||
- Verification time
|
||||
- Expected debugging/fixes time
|
||||
- Documentation updates
|
||||
|
||||
If a phase can't be completed in one session, it should be split into sub-phases (e.g., Phase 3.1, Phase 3.2).
|
||||
|
||||
### Dependencies
|
||||
Phases are ordered to minimize dependencies:
|
||||
1. Infrastructure (no dependencies)
|
||||
2. Database (depends on Infrastructure)
|
||||
3. API (depends on Infrastructure + Database)
|
||||
4. UI (depends on API)
|
||||
5. Integration (depends on relevant phases)
|
||||
6. Testing (depends on all implementation phases)
|
||||
|
||||
### Known Risks
|
||||
[Optional: List any known challenges or uncertainties]
|
||||
- Risk 1: [Description and mitigation]
|
||||
- Risk 2: [Description and mitigation]
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
**Phase Completion**: All verification criteria met, exit criteria satisfied
|
||||
**Session Handoff**: SESSION.md updated, git checkpoint created
|
||||
**Overall Success**: [Define what "done" means for this project]
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial phase breakdown
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
612
templates/INTEGRATION.md
Normal file
612
templates/INTEGRATION.md
Normal file
@@ -0,0 +1,612 @@
|
||||
# Third-Party Integrations: [Project Name]
|
||||
|
||||
**Primary Integrations**: [List main services - e.g., Clerk, Stripe, OpenAI]
|
||||
**Webhooks**: [Number of webhook handlers]
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes all third-party service integrations including API setup, authentication, webhooks, and error handling.
|
||||
|
||||
**Integration Principles**:
|
||||
- **Environment-based config** - API keys in env vars, never committed
|
||||
- **Graceful degradation** - App works (reduced functionality) if service is down
|
||||
- **Webhook verification** - Always verify webhook signatures
|
||||
- **Rate limit awareness** - Respect provider rate limits
|
||||
- **Error handling** - Catch and log integration failures
|
||||
|
||||
---
|
||||
|
||||
## Integrations
|
||||
|
||||
### Clerk (Authentication)
|
||||
|
||||
**Purpose**: User authentication and management
|
||||
**Docs**: https://clerk.com/docs
|
||||
**Dashboard**: https://dashboard.clerk.com
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
npm install @clerk/clerk-react @clerk/backend
|
||||
```
|
||||
|
||||
**Environment Variables**:
|
||||
```env
|
||||
# Frontend
|
||||
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
|
||||
|
||||
# Backend (Wrangler secret)
|
||||
CLERK_SECRET_KEY=sk_test_...
|
||||
```
|
||||
|
||||
**Frontend Integration**:
|
||||
```tsx
|
||||
// src/main.tsx
|
||||
import { ClerkProvider } from '@clerk/clerk-react'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
|
||||
<App />
|
||||
</ClerkProvider>
|
||||
)
|
||||
```
|
||||
|
||||
**Backend Integration** (JWT verification):
|
||||
```typescript
|
||||
// src/middleware/auth.ts
|
||||
import { verifyToken } from '@clerk/backend'
|
||||
|
||||
export async function authMiddleware(c: Context, next: Next) {
|
||||
const token = c.req.header('Authorization')?.replace('Bearer ', '')
|
||||
if (!token) {
|
||||
return c.json({ error: 'Unauthorized' }, 401)
|
||||
}
|
||||
|
||||
try {
|
||||
const verified = await verifyToken(token, {
|
||||
secretKey: c.env.CLERK_SECRET_KEY
|
||||
})
|
||||
c.set('userId', verified.sub)
|
||||
c.set('email', verified.email)
|
||||
await next()
|
||||
} catch (error) {
|
||||
return c.json({ error: 'Invalid token' }, 401)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Custom JWT Template** (configured in Clerk dashboard):
|
||||
```json
|
||||
{
|
||||
"email": "{{user.primary_email_address}}",
|
||||
"userId": "{{user.id}}",
|
||||
"firstName": "{{user.first_name}}",
|
||||
"lastName": "{{user.last_name}}"
|
||||
}
|
||||
```
|
||||
|
||||
**Webhooks**: See webhook section below
|
||||
|
||||
**Rate Limits**: 100 requests/second (Pro plan)
|
||||
|
||||
**Error Handling**:
|
||||
- Token expired → Return 401, frontend refreshes token
|
||||
- Service down → Return 503, show maintenance message
|
||||
|
||||
---
|
||||
|
||||
### [Integration Name - e.g., Stripe]
|
||||
|
||||
**Purpose**: [What this service provides - e.g., Payment processing]
|
||||
**Docs**: [Documentation URL]
|
||||
**Dashboard**: [Dashboard URL]
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
npm install [package-name]
|
||||
```
|
||||
|
||||
**Environment Variables**:
|
||||
```env
|
||||
[SERVICE]_API_KEY=...
|
||||
[SERVICE]_WEBHOOK_SECRET=...
|
||||
```
|
||||
|
||||
**API Client**:
|
||||
```typescript
|
||||
// src/lib/[service]-client.ts
|
||||
import [ServiceSDK] from '[package-name]'
|
||||
|
||||
export function create[Service]Client(apiKey: string) {
|
||||
return new [ServiceSDK]({
|
||||
apiKey,
|
||||
// other config
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Example**:
|
||||
```typescript
|
||||
// In route handler
|
||||
const client = create[Service]Client(c.env.[SERVICE]_API_KEY)
|
||||
const result = await client.[method]({ params })
|
||||
```
|
||||
|
||||
**Webhooks**: [Yes/No - see webhook section if yes]
|
||||
|
||||
**Rate Limits**: [Limits for this service]
|
||||
|
||||
**Error Handling**: [How to handle failures]
|
||||
|
||||
---
|
||||
|
||||
### OpenAI (AI Features) - Example
|
||||
|
||||
**Purpose**: AI-powered features (chat, completions, embeddings)
|
||||
**Docs**: https://platform.openai.com/docs
|
||||
**Dashboard**: https://platform.openai.com
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
npm install openai
|
||||
```
|
||||
|
||||
**Environment Variables**:
|
||||
```env
|
||||
OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
**API Client**:
|
||||
```typescript
|
||||
// src/lib/openai-client.ts
|
||||
import OpenAI from 'openai'
|
||||
|
||||
export function createOpenAIClient(apiKey: string) {
|
||||
return new OpenAI({ apiKey })
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```typescript
|
||||
const openai = createOpenAIClient(c.env.OPENAI_API_KEY)
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: 'gpt-5',
|
||||
messages: [{ role: 'user', content: 'Hello!' }],
|
||||
stream: true
|
||||
})
|
||||
|
||||
// Stream response to client
|
||||
return new Response(response.body, {
|
||||
headers: { 'Content-Type': 'text/event-stream' }
|
||||
})
|
||||
```
|
||||
|
||||
**Rate Limits**:
|
||||
- Free tier: 3 requests/minute
|
||||
- Paid: 10,000 requests/minute
|
||||
|
||||
**Error Handling**:
|
||||
```typescript
|
||||
try {
|
||||
const response = await openai.chat.completions.create({ ... })
|
||||
return response
|
||||
} catch (error) {
|
||||
if (error.code === 'rate_limit_exceeded') {
|
||||
return c.json({ error: 'Too many requests. Please try again later.' }, 429)
|
||||
} else {
|
||||
console.error('OpenAI error:', error)
|
||||
return c.json({ error: 'AI service unavailable' }, 503)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhooks
|
||||
|
||||
Webhooks are HTTP callbacks from third-party services to notify our app of events.
|
||||
|
||||
### Webhook: Clerk User Events
|
||||
|
||||
**Purpose**: Sync user data when Clerk users are created, updated, or deleted
|
||||
|
||||
**Endpoint**: `POST /api/webhooks/clerk`
|
||||
|
||||
**Events**:
|
||||
- `user.created` - New user signed up
|
||||
- `user.updated` - User profile changed
|
||||
- `user.deleted` - User account deleted
|
||||
|
||||
**Payload Example**:
|
||||
```json
|
||||
{
|
||||
"type": "user.created",
|
||||
"data": {
|
||||
"id": "user_abc123",
|
||||
"email_addresses": [
|
||||
{ "email_address": "user@example.com" }
|
||||
],
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Verification**:
|
||||
```typescript
|
||||
import { Webhook } from 'svix'
|
||||
|
||||
export async function verifyClerkWebhook(
|
||||
payload: string,
|
||||
headers: Headers,
|
||||
secret: string
|
||||
) {
|
||||
const wh = new Webhook(secret)
|
||||
|
||||
try {
|
||||
return wh.verify(payload, {
|
||||
'svix-id': headers.get('svix-id')!,
|
||||
'svix-timestamp': headers.get('svix-timestamp')!,
|
||||
'svix-signature': headers.get('svix-signature')!
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error('Invalid webhook signature')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Handler**:
|
||||
```typescript
|
||||
app.post('/api/webhooks/clerk', async (c) => {
|
||||
const payload = await c.req.text()
|
||||
const verified = await verifyClerkWebhook(
|
||||
payload,
|
||||
c.req.raw.headers,
|
||||
c.env.CLERK_WEBHOOK_SECRET
|
||||
)
|
||||
|
||||
const event = JSON.parse(payload)
|
||||
|
||||
switch (event.type) {
|
||||
case 'user.created':
|
||||
await createUserInDatabase(event.data)
|
||||
break
|
||||
case 'user.updated':
|
||||
await updateUserInDatabase(event.data)
|
||||
break
|
||||
case 'user.deleted':
|
||||
await deleteUserInDatabase(event.data.id)
|
||||
break
|
||||
}
|
||||
|
||||
return c.json({ received: true })
|
||||
})
|
||||
```
|
||||
|
||||
**Configuration** (Clerk dashboard):
|
||||
1. Go to Webhooks section
|
||||
2. Add endpoint: `https://[your-app].workers.dev/api/webhooks/clerk`
|
||||
3. Select events: user.created, user.updated, user.deleted
|
||||
4. Copy signing secret to environment variables
|
||||
|
||||
---
|
||||
|
||||
### Webhook: [Service Name]
|
||||
|
||||
**Purpose**: [What events this webhook handles]
|
||||
|
||||
**Endpoint**: `POST /api/webhooks/[service]`
|
||||
|
||||
**Events**: [List of event types]
|
||||
|
||||
**Signature Verification**: [How to verify]
|
||||
|
||||
**Handler**: [Implementation details]
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Development (.dev.vars)
|
||||
```env
|
||||
# Clerk
|
||||
CLERK_SECRET_KEY=sk_test_...
|
||||
CLERK_WEBHOOK_SECRET=whsec_...
|
||||
|
||||
# OpenAI
|
||||
OPENAI_API_KEY=sk-...
|
||||
|
||||
# Stripe
|
||||
STRIPE_SECRET_KEY=sk_test_...
|
||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
|
||||
# [Other services]
|
||||
```
|
||||
|
||||
### Production (Wrangler secrets)
|
||||
```bash
|
||||
# Set secrets via CLI
|
||||
npx wrangler secret put CLERK_SECRET_KEY
|
||||
npx wrangler secret put OPENAI_API_KEY
|
||||
npx wrangler secret put STRIPE_SECRET_KEY
|
||||
|
||||
# Or via dashboard
|
||||
# Cloudflare Dashboard → Workers → [Your Worker] → Settings → Variables
|
||||
```
|
||||
|
||||
**Never commit secrets to git**. Use `.dev.vars` locally (gitignored) and Wrangler secrets in production.
|
||||
|
||||
---
|
||||
|
||||
## API Rate Limiting
|
||||
|
||||
### Handling Rate Limits
|
||||
|
||||
**Strategy**: Exponential backoff + retry
|
||||
|
||||
**Implementation**:
|
||||
```typescript
|
||||
async function callWithRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxRetries = 3
|
||||
): Promise<T> {
|
||||
let attempt = 0
|
||||
|
||||
while (attempt < maxRetries) {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (error) {
|
||||
if (error.code === 'rate_limit' && attempt < maxRetries - 1) {
|
||||
const delay = Math.pow(2, attempt) * 1000 // 1s, 2s, 4s
|
||||
await new Promise(resolve => setTimeout(resolve, delay))
|
||||
attempt++
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Max retries exceeded')
|
||||
}
|
||||
|
||||
// Usage
|
||||
const result = await callWithRetry(() =>
|
||||
openai.chat.completions.create({ ... })
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Integration Failure Patterns
|
||||
|
||||
**1. Service Temporarily Down**:
|
||||
```typescript
|
||||
try {
|
||||
const result = await callExternalService()
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Service error:', error)
|
||||
return c.json({
|
||||
error: 'Service temporarily unavailable. Please try again later.',
|
||||
code: 'SERVICE_UNAVAILABLE'
|
||||
}, 503)
|
||||
}
|
||||
```
|
||||
|
||||
**2. Invalid Credentials**:
|
||||
```typescript
|
||||
if (error.code === 'invalid_api_key') {
|
||||
console.error('Invalid API key for [service]')
|
||||
return c.json({
|
||||
error: 'Configuration error. Please contact support.',
|
||||
code: 'CONFIGURATION_ERROR'
|
||||
}, 500)
|
||||
}
|
||||
```
|
||||
|
||||
**3. Rate Limited**:
|
||||
```typescript
|
||||
if (error.code === 'rate_limit_exceeded') {
|
||||
return c.json({
|
||||
error: 'Too many requests. Please wait a moment and try again.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
retryAfter: error.retryAfter
|
||||
}, 429)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Integrations
|
||||
|
||||
### Mocking External Services
|
||||
|
||||
**Use MSW (Mock Service Worker)** for tests:
|
||||
|
||||
```typescript
|
||||
// tests/mocks/handlers.ts
|
||||
import { http, HttpResponse } from 'msw'
|
||||
|
||||
export const handlers = [
|
||||
http.post('https://api.openai.com/v1/chat/completions', () => {
|
||||
return HttpResponse.json({
|
||||
choices: [
|
||||
{ message: { content: 'Mocked response' } }
|
||||
]
|
||||
})
|
||||
})
|
||||
]
|
||||
```
|
||||
|
||||
**Setup**:
|
||||
```typescript
|
||||
// tests/setup.ts
|
||||
import { setupServer } from 'msw/node'
|
||||
import { handlers } from './mocks/handlers'
|
||||
|
||||
const server = setupServer(...handlers)
|
||||
|
||||
beforeAll(() => server.listen())
|
||||
afterEach(() => server.resetHandlers())
|
||||
afterAll(() => server.close())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Testing Webhooks
|
||||
|
||||
**Test webhook signature verification**:
|
||||
```typescript
|
||||
describe('POST /api/webhooks/clerk', () => {
|
||||
it('rejects invalid signature', async () => {
|
||||
const res = await app.request('/api/webhooks/clerk', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'svix-signature': 'invalid_signature'
|
||||
},
|
||||
body: JSON.stringify({ type: 'user.created', data: {} })
|
||||
})
|
||||
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
it('processes valid webhook', async () => {
|
||||
// Generate valid signature for testing
|
||||
const payload = JSON.stringify({ type: 'user.created', data: {...} })
|
||||
const signature = generateTestSignature(payload)
|
||||
|
||||
const res = await app.request('/api/webhooks/clerk', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'svix-signature': signature,
|
||||
'svix-id': 'msg_123',
|
||||
'svix-timestamp': Date.now().toString()
|
||||
},
|
||||
body: payload
|
||||
})
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Track Integration Health
|
||||
|
||||
**Metrics**:
|
||||
- API call success rate (per service)
|
||||
- API call latency (average, p95, p99)
|
||||
- Rate limit hits
|
||||
- Webhook delivery success rate
|
||||
|
||||
**Logging**:
|
||||
```typescript
|
||||
console.log('[Integration]', {
|
||||
service: 'openai',
|
||||
method: 'chat.completions.create',
|
||||
success: true,
|
||||
latency: responseTime,
|
||||
tokensUsed: response.usage.total_tokens
|
||||
})
|
||||
```
|
||||
|
||||
**Alerts**:
|
||||
- Integration success rate < 95% → Alert
|
||||
- Average latency > 5s → Alert
|
||||
- Webhook failures > 10 in 1 hour → Alert
|
||||
|
||||
---
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
**If integration fails, app should still function** (with reduced features).
|
||||
|
||||
**Example**: AI chat unavailable
|
||||
```typescript
|
||||
try {
|
||||
const response = await openai.chat.completions.create({ ... })
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('OpenAI unavailable:', error)
|
||||
|
||||
// Fallback: Return canned response
|
||||
return {
|
||||
content: "I'm currently unavailable. Please try again later or contact support.",
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example**: Payment processing down
|
||||
```typescript
|
||||
if (!stripe.isHealthy()) {
|
||||
return (
|
||||
<Alert variant="warning">
|
||||
Payment processing is temporarily unavailable. Please try again later.
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### API Keys
|
||||
- ✅ Store in environment variables
|
||||
- ✅ Use Wrangler secrets in production
|
||||
- ✅ Rotate keys periodically
|
||||
- ❌ Never commit to git
|
||||
- ❌ Never log in production
|
||||
|
||||
### Webhook Security
|
||||
- ✅ Always verify signatures
|
||||
- ✅ Use HTTPS endpoints only
|
||||
- ✅ Validate payload structure
|
||||
- ✅ Implement replay protection (check timestamp)
|
||||
- ❌ Never trust webhook data without verification
|
||||
|
||||
### CORS
|
||||
- ✅ Restrict origins to your domain
|
||||
- ✅ Allow only necessary methods
|
||||
- ❌ Don't use wildcard (*) in production
|
||||
|
||||
---
|
||||
|
||||
## Future Integrations
|
||||
|
||||
Planned integrations:
|
||||
- [ ] [Service name] - [Purpose]
|
||||
- [ ] [Service name] - [Purpose]
|
||||
|
||||
---
|
||||
|
||||
## Integration Checklist
|
||||
|
||||
When adding a new integration:
|
||||
- [ ] Install SDK/package
|
||||
- [ ] Add environment variables
|
||||
- [ ] Create API client wrapper
|
||||
- [ ] Implement error handling
|
||||
- [ ] Add rate limit handling
|
||||
- [ ] Setup webhook handler (if applicable)
|
||||
- [ ] Verify webhook signatures
|
||||
- [ ] Write tests (mock external calls)
|
||||
- [ ] Document in this file
|
||||
- [ ] Add monitoring/logging
|
||||
- [ ] Test in staging before production
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial integration documentation
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
508
templates/TESTING.md
Normal file
508
templates/TESTING.md
Normal file
@@ -0,0 +1,508 @@
|
||||
# Testing Strategy: [Project Name]
|
||||
|
||||
**Testing Framework**: Vitest (unit/integration)
|
||||
**E2E Framework**: Playwright (optional)
|
||||
**Coverage Target**: 70%+ for critical paths
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the testing strategy including what to test, how to test, and when to test.
|
||||
|
||||
**Testing Philosophy**:
|
||||
- **Test behavior, not implementation** - Focus on user-facing functionality
|
||||
- **Test critical paths first** - Auth, data CRUD, payments, etc
|
||||
- **Fast feedback** - Unit tests run in milliseconds
|
||||
- **Realistic tests** - Integration tests use real-ish data
|
||||
- **E2E for happy paths** - Cover main user workflows
|
||||
|
||||
**Testing Pyramid**:
|
||||
```
|
||||
┌──────────┐
|
||||
│ E2E │ ← Few (slow, brittle)
|
||||
└──────────┘
|
||||
┌──────────────┐
|
||||
│ Integration │ ← Some (medium speed)
|
||||
└──────────────┘
|
||||
┌───────────────────┐
|
||||
│ Unit Tests │ ← Many (fast, focused)
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What to Test
|
||||
|
||||
### ✅ Do Test
|
||||
|
||||
**Business Logic**:
|
||||
- Data validation (Zod schemas)
|
||||
- Data transformations
|
||||
- Complex calculations
|
||||
- State management logic
|
||||
|
||||
**API Endpoints**:
|
||||
- All HTTP methods (GET, POST, PATCH, DELETE)
|
||||
- All response codes (200, 400, 401, 404, 500)
|
||||
- Request validation
|
||||
- Authorization checks
|
||||
|
||||
**User Workflows**:
|
||||
- Authentication flow
|
||||
- CRUD operations (create, read, update, delete)
|
||||
- Form submissions
|
||||
- Error handling
|
||||
|
||||
**Critical Paths**:
|
||||
- Payment processing (if applicable)
|
||||
- Data export/import
|
||||
- Email notifications
|
||||
- File uploads
|
||||
|
||||
---
|
||||
|
||||
### ❌ Don't Test
|
||||
|
||||
**Third-party libraries**: Trust that React, Tailwind, shadcn/ui work
|
||||
|
||||
**Implementation details**: Internal function calls, component props (unless part of public API)
|
||||
|
||||
**Trivial code**: Simple getters/setters, pass-through functions
|
||||
|
||||
**UI styling**: Visual regression testing is overkill for most projects
|
||||
|
||||
---
|
||||
|
||||
## Testing Layers
|
||||
|
||||
### 1. Unit Tests (Many)
|
||||
|
||||
**Purpose**: Test individual functions and utilities in isolation
|
||||
|
||||
**Tool**: Vitest
|
||||
|
||||
**What to test**:
|
||||
- Utility functions (`src/lib/utils.ts`)
|
||||
- Zod schemas (`src/lib/schemas.ts`)
|
||||
- Data transformations
|
||||
- Pure functions
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
// src/lib/utils.test.ts
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { formatDate, cn } from './utils'
|
||||
|
||||
describe('formatDate', () => {
|
||||
it('formats Unix timestamp to readable date', () => {
|
||||
expect(formatDate(1234567890)).toBe('Feb 14, 2009')
|
||||
})
|
||||
|
||||
it('handles invalid timestamps', () => {
|
||||
expect(formatDate(-1)).toBe('Invalid Date')
|
||||
})
|
||||
})
|
||||
|
||||
describe('cn', () => {
|
||||
it('merges class names', () => {
|
||||
expect(cn('foo', 'bar')).toBe('foo bar')
|
||||
})
|
||||
|
||||
it('handles conditional classes', () => {
|
||||
expect(cn('foo', false && 'bar', 'baz')).toBe('foo baz')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Run**: `npm run test`
|
||||
|
||||
---
|
||||
|
||||
### 2. Integration Tests (Some)
|
||||
|
||||
**Purpose**: Test API endpoints with real database interactions
|
||||
|
||||
**Tool**: Vitest + Miniflare (Cloudflare Workers simulator)
|
||||
|
||||
**What to test**:
|
||||
- API routes (`/api/*`)
|
||||
- Middleware (auth, CORS, error handling)
|
||||
- Database operations (CRUD)
|
||||
|
||||
**Setup**:
|
||||
```typescript
|
||||
// tests/setup.ts
|
||||
import { beforeAll, afterAll, beforeEach } from 'vitest'
|
||||
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test'
|
||||
|
||||
beforeAll(async () => {
|
||||
// Setup test database
|
||||
await env.DB.exec('CREATE TABLE IF NOT EXISTS users (...)')
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
// Clear database before each test
|
||||
await env.DB.exec('DELETE FROM users')
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// Cleanup
|
||||
})
|
||||
```
|
||||
|
||||
**Example Test**:
|
||||
```typescript
|
||||
// tests/api/tasks.test.ts
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import app from '../../src/index'
|
||||
|
||||
describe('POST /api/tasks', () => {
|
||||
it('creates a task for authenticated user', async () => {
|
||||
const res = await app.request('/api/tasks', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer valid_jwt_token'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: 'Test Task',
|
||||
description: 'Test Description'
|
||||
})
|
||||
})
|
||||
|
||||
expect(res.status).toBe(201)
|
||||
const data = await res.json()
|
||||
expect(data.title).toBe('Test Task')
|
||||
})
|
||||
|
||||
it('returns 401 without auth', async () => {
|
||||
const res = await app.request('/api/tasks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: 'Test' })
|
||||
})
|
||||
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
it('returns 400 with invalid data', async () => {
|
||||
const res = await app.request('/api/tasks', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer valid_jwt_token'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: '' // invalid: empty title
|
||||
})
|
||||
})
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Run**: `npm run test`
|
||||
|
||||
---
|
||||
|
||||
### 3. E2E Tests (Few) - Optional
|
||||
|
||||
**Purpose**: Test complete user workflows in real browser
|
||||
|
||||
**Tool**: Playwright
|
||||
|
||||
**What to test**:
|
||||
- Authentication flow (sign up, sign in, sign out)
|
||||
- Main CRUD workflows
|
||||
- Critical paths (payments, exports, etc)
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
npm install -D @playwright/test
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
**Example Test**:
|
||||
```typescript
|
||||
// e2e/auth.spec.ts
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('user can sign in and create a task', async ({ page }) => {
|
||||
// Sign in
|
||||
await page.goto('http://localhost:5173')
|
||||
await page.click('text=Sign In')
|
||||
await page.fill('input[name="email"]', 'test@example.com')
|
||||
await page.fill('input[name="password"]', 'password123')
|
||||
await page.click('button[type="submit"]')
|
||||
|
||||
// Wait for redirect to dashboard
|
||||
await expect(page).toHaveURL(/\/dashboard/)
|
||||
|
||||
// Create task
|
||||
await page.click('text=Create Task')
|
||||
await page.fill('input[name="title"]', 'New Task')
|
||||
await page.fill('textarea[name="description"]', 'Task description')
|
||||
await page.click('button:has-text("Save")')
|
||||
|
||||
// Verify task appears
|
||||
await expect(page.locator('text=New Task')).toBeVisible()
|
||||
})
|
||||
```
|
||||
|
||||
**Run**: `npm run test:e2e`
|
||||
|
||||
---
|
||||
|
||||
## Test Coverage
|
||||
|
||||
**Target Coverage**: 70%+ for critical code
|
||||
|
||||
**What to cover**:
|
||||
- ✅ All API routes
|
||||
- ✅ All middleware
|
||||
- ✅ Business logic and utilities
|
||||
- ✅ Zod schemas (validation tests)
|
||||
- ⚠️ React components (optional - prefer E2E for UI)
|
||||
|
||||
**Generate Coverage Report**:
|
||||
```bash
|
||||
npm run test -- --coverage
|
||||
```
|
||||
|
||||
**View Report**: `coverage/index.html`
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before marking a phase complete, verify:
|
||||
|
||||
### API Phase
|
||||
- [ ] All endpoints return correct status codes (200, 400, 401, 404, 500)
|
||||
- [ ] Request validation works (invalid data → 400)
|
||||
- [ ] Authorization works (no token → 401, wrong user → 403)
|
||||
- [ ] Database operations succeed
|
||||
- [ ] Error handling catches exceptions
|
||||
|
||||
### UI Phase
|
||||
- [ ] Forms validate input (Zod schemas)
|
||||
- [ ] Forms show error messages
|
||||
- [ ] Loading states display correctly
|
||||
- [ ] Error states display correctly
|
||||
- [ ] Happy path works (create, read, update, delete)
|
||||
|
||||
### Integration Phase
|
||||
- [ ] Third-party service integration works
|
||||
- [ ] Webhooks fire correctly
|
||||
- [ ] Error handling for external failures
|
||||
|
||||
---
|
||||
|
||||
## Manual Testing
|
||||
|
||||
**Automated tests don't catch everything**. Manually test:
|
||||
|
||||
### Before Each Deployment
|
||||
- [ ] Sign in works
|
||||
- [ ] Main workflows work (create, edit, delete)
|
||||
- [ ] Forms validate correctly
|
||||
- [ ] Errors display properly
|
||||
- [ ] Dark/light mode works
|
||||
- [ ] Mobile layout works
|
||||
|
||||
### Smoke Test Checklist
|
||||
```
|
||||
1. Visit homepage → Should load
|
||||
2. Click Sign In → Should show Clerk modal
|
||||
3. Sign in → Should redirect to dashboard
|
||||
4. Create [resource] → Should appear in list
|
||||
5. Edit [resource] → Changes should save
|
||||
6. Delete [resource] → Should remove from list
|
||||
7. Sign out → Should return to homepage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Continuous Integration (CI)
|
||||
|
||||
**GitHub Actions** (optional):
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- run: npm install
|
||||
- run: npm run test
|
||||
- run: npm run build
|
||||
```
|
||||
|
||||
**Run tests on**:
|
||||
- Every push
|
||||
- Every pull request
|
||||
- Before deployment
|
||||
|
||||
---
|
||||
|
||||
## Test Data
|
||||
|
||||
### Seed Data for Testing
|
||||
|
||||
Create `migrations/seed.sql` with realistic test data:
|
||||
|
||||
```sql
|
||||
INSERT INTO users (email, clerk_id, display_name, created_at, updated_at)
|
||||
VALUES
|
||||
('alice@example.com', 'clerk_alice', 'Alice', strftime('%s', 'now'), strftime('%s', 'now')),
|
||||
('bob@example.com', 'clerk_bob', 'Bob', strftime('%s', 'now'), strftime('%s', 'now'));
|
||||
|
||||
INSERT INTO tasks (user_id, title, description, created_at, updated_at)
|
||||
VALUES
|
||||
(1, 'Review PR', 'Review the new feature PR', strftime('%s', 'now'), strftime('%s', 'now')),
|
||||
(1, 'Write tests', 'Add tests for API endpoints', strftime('%s', 'now'), strftime('%s', 'now')),
|
||||
(2, 'Deploy app', 'Deploy to production', strftime('%s', 'now'), strftime('%s', 'now'));
|
||||
```
|
||||
|
||||
**Load seed data**:
|
||||
```bash
|
||||
npx wrangler d1 execute [DB_NAME] --local --file=migrations/seed.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Tools
|
||||
|
||||
### Installed
|
||||
- **Vitest**: Unit and integration tests
|
||||
- **@cloudflare/vitest-pool-workers**: Test Workers in Miniflare
|
||||
- **@vitest/coverage-v8**: Code coverage
|
||||
|
||||
### Optional
|
||||
- **Playwright**: E2E browser testing
|
||||
- **@testing-library/react**: React component testing (if needed)
|
||||
- **MSW**: Mock Service Worker (mock external APIs)
|
||||
|
||||
---
|
||||
|
||||
## Test Scripts
|
||||
|
||||
**package.json**:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"test:e2e": "playwright test"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Run tests**:
|
||||
```bash
|
||||
npm run test # Run all tests (watch mode)
|
||||
npm run test:coverage # Generate coverage report
|
||||
npm run test:e2e # Run E2E tests (Playwright)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
**Vitest UI** (visual test runner):
|
||||
```bash
|
||||
npm run test:ui
|
||||
```
|
||||
|
||||
**Debug single test**:
|
||||
```typescript
|
||||
it.only('specific test', () => {
|
||||
// Only this test runs
|
||||
})
|
||||
```
|
||||
|
||||
**Console logs in tests**: They appear in terminal output
|
||||
|
||||
**Playwright debug mode**:
|
||||
```bash
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Arrange, Act, Assert** - Structure tests clearly
|
||||
- **One assertion per test** (when possible)
|
||||
- **Descriptive test names** - "should return 401 when token is missing"
|
||||
- **Test edge cases** - Empty strings, null values, large numbers
|
||||
- **Use realistic data** - Test with production-like data
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Test implementation details** - Internal state, private methods
|
||||
- **Over-mock** - Use real database in integration tests
|
||||
- **Brittle selectors** - Avoid testing specific CSS classes
|
||||
- **Flaky tests** - Fix immediately or remove
|
||||
- **Slow tests** - Optimize or move to E2E
|
||||
|
||||
---
|
||||
|
||||
## Test-Driven Development (TDD) - Optional
|
||||
|
||||
**For critical features**, consider writing tests first:
|
||||
|
||||
1. Write failing test
|
||||
2. Implement feature (test passes)
|
||||
3. Refactor
|
||||
4. Repeat
|
||||
|
||||
**Benefits**: Better design, fewer bugs, built-in documentation
|
||||
|
||||
**When to use**: Complex business logic, critical paths, bug fixes
|
||||
|
||||
---
|
||||
|
||||
## Monitoring Test Health
|
||||
|
||||
**Metrics to track**:
|
||||
- Test pass rate (should be 100%)
|
||||
- Test coverage (target 70%+)
|
||||
- Test execution time (keep fast)
|
||||
- Flaky test count (should be 0)
|
||||
|
||||
**Weekly review**:
|
||||
- Are tests passing?
|
||||
- Is coverage dropping?
|
||||
- Are tests slowing down?
|
||||
- Any flaky tests to fix?
|
||||
|
||||
---
|
||||
|
||||
## Future Testing Enhancements
|
||||
|
||||
- [ ] Add visual regression testing (if needed)
|
||||
- [ ] Add performance testing (if needed)
|
||||
- [ ] Add accessibility testing (axe-core)
|
||||
- [ ] Add API contract testing (Pact)
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial testing strategy
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
653
templates/UI_COMPONENTS.md
Normal file
653
templates/UI_COMPONENTS.md
Normal file
@@ -0,0 +1,653 @@
|
||||
# UI Components: [Project Name]
|
||||
|
||||
**Framework**: React 19
|
||||
**Component Library**: shadcn/ui (Radix UI primitives)
|
||||
**Styling**: Tailwind v4
|
||||
**Forms**: React Hook Form + Zod
|
||||
**State**: TanStack Query (server) + Zustand (client)
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the component hierarchy, reusable components, forms, and UI patterns.
|
||||
|
||||
**Component Philosophy**:
|
||||
- **Composition over configuration** - Build complex UIs from simple parts
|
||||
- **Accessibility first** - All components keyboard navigable, ARIA compliant
|
||||
- **shadcn/ui ownership** - Components copied to codebase, not npm dependency
|
||||
- **Tailwind utility classes** - Minimal custom CSS
|
||||
- **Dark mode support** - All components adapt to theme
|
||||
|
||||
---
|
||||
|
||||
## Component Hierarchy
|
||||
|
||||
```
|
||||
App
|
||||
├── Providers
|
||||
│ ├── ClerkProvider (auth)
|
||||
│ ├── ThemeProvider (dark/light mode)
|
||||
│ └── QueryClientProvider (TanStack Query)
|
||||
│
|
||||
├── Layout
|
||||
│ ├── Header
|
||||
│ │ ├── Logo
|
||||
│ │ ├── Navigation
|
||||
│ │ └── UserMenu
|
||||
│ ├── Main (page content)
|
||||
│ └── Footer (optional)
|
||||
│
|
||||
└── Pages (routes)
|
||||
├── HomePage
|
||||
│ ├── HeroSection
|
||||
│ ├── FeaturesSection
|
||||
│ └── CTASection
|
||||
│
|
||||
├── DashboardPage
|
||||
│ ├── Sidebar
|
||||
│ │ └── NavLinks
|
||||
│ └── DashboardContent
|
||||
│ ├── StatsCards
|
||||
│ └── [ResourceList]
|
||||
│
|
||||
├── [Resource]Page
|
||||
│ ├── [Resource]List
|
||||
│ │ ├── [Resource]Card (for each item)
|
||||
│ │ └── Pagination
|
||||
│ └── [Resource]CreateDialog
|
||||
│
|
||||
└── [Resource]DetailPage
|
||||
├── [Resource]Header
|
||||
├── [Resource]Info
|
||||
└── [Resource]Actions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Layout Components
|
||||
|
||||
### `App.tsx`
|
||||
**Purpose**: Root component with providers
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
import { ClerkProvider } from '@clerk/clerk-react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { ThemeProvider } from '@/components/theme-provider'
|
||||
import { Router } from '@/router'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<ClerkProvider publishableKey={...}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider defaultTheme="system" storageKey="app-theme">
|
||||
<Router />
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</ClerkProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Layout.tsx`
|
||||
**Purpose**: Common layout wrapper for authenticated pages
|
||||
|
||||
**Props**: None (uses `Outlet` from react-router)
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function Layout() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Header />
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Files**: `src/components/layout/Layout.tsx`, `Header.tsx`, `Footer.tsx`
|
||||
|
||||
---
|
||||
|
||||
### `Header.tsx`
|
||||
**Purpose**: Top navigation bar
|
||||
|
||||
**Features**:
|
||||
- Logo/brand
|
||||
- Navigation links
|
||||
- User menu (authenticated) or Sign In button (unauthenticated)
|
||||
- Theme toggle (dark/light mode)
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function Header() {
|
||||
return (
|
||||
<header className="border-b bg-background">
|
||||
<div className="container mx-auto flex h-16 items-center justify-between px-4">
|
||||
<div className="flex items-center gap-6">
|
||||
<Logo />
|
||||
<Navigation />
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<ThemeToggle />
|
||||
<UserButton />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Files**: `src/components/layout/Header.tsx`
|
||||
|
||||
---
|
||||
|
||||
## shadcn/ui Components Used
|
||||
|
||||
### Installed Components
|
||||
Run these to add components:
|
||||
```bash
|
||||
pnpm dlx shadcn@latest add button
|
||||
pnpm dlx shadcn@latest add dialog
|
||||
pnpm dlx shadcn@latest add dropdown-menu
|
||||
pnpm dlx shadcn@latest add form
|
||||
pnpm dlx shadcn@latest add input
|
||||
pnpm dlx shadcn@latest add label
|
||||
pnpm dlx shadcn@latest add select
|
||||
pnpm dlx shadcn@latest add textarea
|
||||
pnpm dlx shadcn@latest add card
|
||||
pnpm dlx shadcn@latest add badge
|
||||
pnpm dlx shadcn@latest add avatar
|
||||
pnpm dlx shadcn@latest add skeleton
|
||||
pnpm dlx shadcn@latest add toast
|
||||
```
|
||||
|
||||
**Location**: `src/components/ui/`
|
||||
|
||||
**Ownership**: These are now part of our codebase, modify as needed
|
||||
|
||||
---
|
||||
|
||||
## Custom Components
|
||||
|
||||
### `[Resource]List.tsx`
|
||||
**Purpose**: Display list of [resources] with loading/error states
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface [Resource]ListProps {
|
||||
// No props - uses TanStack Query internally
|
||||
}
|
||||
```
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function [Resource]List() {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['[resources]'],
|
||||
queryFn: fetch[Resources]
|
||||
})
|
||||
|
||||
if (isLoading) return <[Resource]ListSkeleton />
|
||||
if (error) return <ErrorMessage error={error} />
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{data.map(item => (
|
||||
<[Resource]Card key={item.id} item={item} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Files**: `src/components/[resource]/[Resource]List.tsx`
|
||||
|
||||
---
|
||||
|
||||
### `[Resource]Card.tsx`
|
||||
**Purpose**: Display single [resource] item
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface [Resource]CardProps {
|
||||
item: [Resource]
|
||||
onEdit?: (id: number) => void
|
||||
onDelete?: (id: number) => void
|
||||
}
|
||||
```
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function [Resource]Card({ item, onEdit, onDelete }: [Resource]CardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>{item.description}</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button onClick={() => onEdit?.(item.id)}>Edit</Button>
|
||||
<Button variant="destructive" onClick={() => onDelete?.(item.id)}>Delete</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Files**: `src/components/[resource]/[Resource]Card.tsx`
|
||||
|
||||
---
|
||||
|
||||
### `[Resource]Form.tsx`
|
||||
**Purpose**: Create or edit [resource]
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface [Resource]FormProps {
|
||||
item?: [Resource] // undefined for create, populated for edit
|
||||
onSuccess?: () => void
|
||||
}
|
||||
```
|
||||
|
||||
**Structure** (using React Hook Form + Zod):
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { [resource]Schema } from '@/lib/schemas'
|
||||
|
||||
export function [Resource]Form({ item, onSuccess }: [Resource]FormProps) {
|
||||
const form = useForm({
|
||||
resolver: zodResolver([resource]Schema),
|
||||
defaultValues: item || {
|
||||
field1: '',
|
||||
field2: ''
|
||||
}
|
||||
})
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: item ? update[Resource] : create[Resource],
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['[resources]'] })
|
||||
onSuccess?.()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(data => mutation.mutate(data))}>
|
||||
<FormField name="field1" control={form.control} render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Field 1</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Files**: `src/components/[resource]/[Resource]Form.tsx`
|
||||
|
||||
---
|
||||
|
||||
### `[Resource]CreateDialog.tsx`
|
||||
**Purpose**: Modal dialog for creating new [resource]
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function [Resource]CreateDialog() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Create [Resource]</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New [Resource]</DialogTitle>
|
||||
</DialogHeader>
|
||||
<[Resource]Form onSuccess={() => setOpen(false)} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Files**: `src/components/[resource]/[Resource]CreateDialog.tsx`
|
||||
|
||||
---
|
||||
|
||||
## Loading and Error States
|
||||
|
||||
### `[Resource]ListSkeleton.tsx`
|
||||
**Purpose**: Loading state for [resource] list
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function [Resource]ListSkeleton() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map(i => (
|
||||
<Card key={i}>
|
||||
<CardHeader>
|
||||
<Skeleton className="h-6 w-48" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-3/4 mt-2" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ErrorMessage.tsx`
|
||||
**Purpose**: Reusable error display
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface ErrorMessageProps {
|
||||
error: Error
|
||||
retry?: () => void
|
||||
}
|
||||
```
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function ErrorMessage({ error, retry }: ErrorMessageProps) {
|
||||
return (
|
||||
<div className="rounded-lg border border-destructive bg-destructive/10 p-4">
|
||||
<h3 className="font-semibold text-destructive">Error</h3>
|
||||
<p className="text-sm text-muted-foreground">{error.message}</p>
|
||||
{retry && (
|
||||
<Button variant="outline" onClick={retry} className="mt-2">
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management Patterns
|
||||
|
||||
### Server State (TanStack Query)
|
||||
|
||||
**Use for**: Data from API (users, tasks, analytics, etc)
|
||||
|
||||
**Example**:
|
||||
```tsx
|
||||
// In component
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['tasks'],
|
||||
queryFn: () => api.get('/api/tasks').then(res => res.data)
|
||||
})
|
||||
|
||||
// Mutation
|
||||
const mutation = useMutation({
|
||||
mutationFn: (newTask) => api.post('/api/tasks', newTask),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Files**: Queries/mutations defined in `src/lib/api.ts` or component files
|
||||
|
||||
---
|
||||
|
||||
### Client State (Zustand)
|
||||
|
||||
**Use for**: UI state, preferences, filters
|
||||
|
||||
**Example Store**:
|
||||
```tsx
|
||||
// src/stores/ui-store.ts
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
interface UIStore {
|
||||
sidebarOpen: boolean
|
||||
setSidebarOpen: (open: boolean) => void
|
||||
theme: 'light' | 'dark' | 'system'
|
||||
setTheme: (theme: 'light' | 'dark' | 'system') => void
|
||||
}
|
||||
|
||||
export const useUIStore = create<UIStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
sidebarOpen: true,
|
||||
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
||||
theme: 'system',
|
||||
setTheme: (theme) => set({ theme })
|
||||
}),
|
||||
{ name: 'ui-store' }
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
const { sidebarOpen, setSidebarOpen } = useUIStore()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Theme System
|
||||
|
||||
### ThemeProvider
|
||||
**Purpose**: Manage dark/light/system theme
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
// src/components/theme-provider.tsx
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return (
|
||||
<NextThemesProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
return useNextTheme()
|
||||
}
|
||||
```
|
||||
|
||||
**Files**: `src/components/theme-provider.tsx`
|
||||
|
||||
---
|
||||
|
||||
### ThemeToggle
|
||||
**Purpose**: Toggle between light/dark/system themes
|
||||
|
||||
**Structure**:
|
||||
```tsx
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Sun className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => setTheme('light')}>Light</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme('dark')}>Dark</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme('system')}>System</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Form Patterns
|
||||
|
||||
All forms use **React Hook Form + Zod** for validation.
|
||||
|
||||
**Schema Definition** (shared client + server):
|
||||
```typescript
|
||||
// src/lib/schemas.ts
|
||||
import { z } from 'zod'
|
||||
|
||||
export const taskSchema = z.object({
|
||||
title: z.string().min(1, 'Title required').max(100),
|
||||
description: z.string().optional(),
|
||||
dueDate: z.string().optional(),
|
||||
priority: z.enum(['low', 'medium', 'high']).default('medium')
|
||||
})
|
||||
|
||||
export type TaskFormData = z.infer<typeof taskSchema>
|
||||
```
|
||||
|
||||
**Form Component**:
|
||||
```tsx
|
||||
const form = useForm<TaskFormData>({
|
||||
resolver: zodResolver(taskSchema),
|
||||
defaultValues: { ... }
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Responsive Design
|
||||
|
||||
**Breakpoints** (Tailwind defaults):
|
||||
- `sm`: 640px
|
||||
- `md`: 768px
|
||||
- `lg`: 1024px
|
||||
- `xl`: 1280px
|
||||
- `2xl`: 1536px
|
||||
|
||||
**Patterns**:
|
||||
```tsx
|
||||
// Stack on mobile, grid on desktop
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
// Hide on mobile, show on desktop
|
||||
<div className="hidden md:block">
|
||||
|
||||
// Different padding on mobile vs desktop
|
||||
<div className="px-4 md:px-8">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
**Requirements**:
|
||||
- All interactive elements keyboard navigable
|
||||
- Focus states visible (use `ring` classes)
|
||||
- Images have alt text
|
||||
- Forms have associated labels
|
||||
- Color contrast meets WCAG AA
|
||||
- Screen reader friendly (ARIA labels)
|
||||
|
||||
**shadcn/ui benefits**: All components built with Radix UI (accessible by default)
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── ui/ # shadcn/ui components
|
||||
│ │ ├── button.tsx
|
||||
│ │ ├── dialog.tsx
|
||||
│ │ ├── form.tsx
|
||||
│ │ └── ...
|
||||
│ ├── layout/ # Layout components
|
||||
│ │ ├── Header.tsx
|
||||
│ │ ├── Footer.tsx
|
||||
│ │ └── Layout.tsx
|
||||
│ ├── [resource]/ # Feature-specific components
|
||||
│ │ ├── [Resource]List.tsx
|
||||
│ │ ├── [Resource]Card.tsx
|
||||
│ │ ├── [Resource]Form.tsx
|
||||
│ │ └── [Resource]CreateDialog.tsx
|
||||
│ ├── theme-provider.tsx
|
||||
│ └── error-message.tsx
|
||||
├── lib/
|
||||
│ ├── schemas.ts # Zod schemas
|
||||
│ ├── api.ts # API client
|
||||
│ └── utils.ts # cn() helper
|
||||
├── stores/
|
||||
│ └── ui-store.ts # Zustand stores
|
||||
└── pages/
|
||||
├── HomePage.tsx
|
||||
├── DashboardPage.tsx
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens
|
||||
|
||||
See `src/index.css` for theme configuration.
|
||||
|
||||
**Colors** (semantic):
|
||||
- `background` / `foreground`
|
||||
- `primary` / `primary-foreground`
|
||||
- `secondary` / `secondary-foreground`
|
||||
- `destructive` / `destructive-foreground`
|
||||
- `muted` / `muted-foreground`
|
||||
- `accent` / `accent-foreground`
|
||||
- `border`
|
||||
- `ring`
|
||||
|
||||
**Usage**: These adapt to light/dark mode automatically.
|
||||
|
||||
---
|
||||
|
||||
## Future Components
|
||||
|
||||
Components to build:
|
||||
- [ ] `[Component]` - [Description]
|
||||
- [ ] `[Component]` - [Description]
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
**v1.0** ([Date]): Initial component structure
|
||||
**v1.1** ([Date]): [Changes made]
|
||||
Reference in New Issue
Block a user