mirror of https://github.com/buster-so/buster.git
Update documentation for future background agents
This commit is contained in:
parent
7b24d167b9
commit
e44cda8f10
|
@ -0,0 +1,196 @@
|
||||||
|
---
|
||||||
|
globs: src/*
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Buster Server API Patterns
|
||||||
|
|
||||||
|
## Modular Route Architecture
|
||||||
|
|
||||||
|
### File Structure Pattern
|
||||||
|
Create separate files for each HTTP method within feature directories:
|
||||||
|
```
|
||||||
|
src/api/v2/[resource]/
|
||||||
|
├── GET.ts # GET operations
|
||||||
|
├── PUT.ts # PUT operations
|
||||||
|
├── POST.ts # POST operations
|
||||||
|
├── DELETE.ts # DELETE operations
|
||||||
|
└── index.ts # Barrel export combining all methods
|
||||||
|
```
|
||||||
|
|
||||||
|
### Barrel Export Pattern (index.ts)
|
||||||
|
Always combine route methods through barrel exports with global middleware:
|
||||||
|
```typescript
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { requireAuth } from '../../../middleware/auth';
|
||||||
|
import GET from './GET';
|
||||||
|
import PUT from './PUT';
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', requireAuth) // Global auth for ALL methods
|
||||||
|
.route('/', GET) // Mount individual handlers
|
||||||
|
.route('/', PUT);
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Middleware Patterns
|
||||||
|
|
||||||
|
### Layered Authentication
|
||||||
|
1. **Global Level**: Apply `requireAuth` in index.ts for ALL routes
|
||||||
|
2. **Method Level**: Apply specific authorization in individual files:
|
||||||
|
- `requireOrganization` - User must belong to organization
|
||||||
|
- `requireOrganizationAdmin` - User must be organization admin
|
||||||
|
- `requireWorkspaceAccess` - User must have workspace access
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In individual route files (GET.ts, PUT.ts, etc.)
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', requireOrganizationAdmin) // Method-specific auth
|
||||||
|
.put('/', zValidator('json', RequestSchema), handler);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Safety Requirements
|
||||||
|
|
||||||
|
### Schema Definition Pattern
|
||||||
|
Define all schemas in `@buster/server-shared/[feature]`:
|
||||||
|
```typescript
|
||||||
|
// @buster/server-shared/organization/types.ts
|
||||||
|
export const UpdateOrganizationRequestSchema = z.object({
|
||||||
|
colorPalette: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UpdateOrganizationRequest = z.infer<typeof UpdateOrganizationRequestSchema>;
|
||||||
|
export type UpdateOrganizationResponse = {
|
||||||
|
id: string;
|
||||||
|
// ... fields
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import Pattern
|
||||||
|
**CRITICAL**: Always use `type` keyword for type imports to minimize build size:
|
||||||
|
```typescript
|
||||||
|
import type { RequestType, ResponseType } from '@buster/server-shared/feature';
|
||||||
|
import { RequestSchema } from '@buster/server-shared/feature';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation Pattern
|
||||||
|
Use Hono's `zValidator` for ALL request validation:
|
||||||
|
```typescript
|
||||||
|
import { zValidator } from '@hono/zod-validator';
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
.put('/', zValidator('json', RequestSchema), async (c) => {
|
||||||
|
const request = c.req.valid('json'); // Fully typed
|
||||||
|
// handler logic
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Interaction Rules
|
||||||
|
|
||||||
|
### Required Pattern
|
||||||
|
**ALL database operations MUST go through `@buster/database` package:**
|
||||||
|
```typescript
|
||||||
|
// ✅ CORRECT
|
||||||
|
import { getOrganization, updateOrganization } from '@buster/database';
|
||||||
|
const org = await getOrganization({ organizationId });
|
||||||
|
|
||||||
|
// ❌ FORBIDDEN - No direct database queries
|
||||||
|
// const result = await db.query('SELECT...');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling Strategy
|
||||||
|
|
||||||
|
### Use Shared Error Utilities
|
||||||
|
Use standardized error handling from `@/utils/response` for consistency:
|
||||||
|
```typescript
|
||||||
|
import { standardErrorHandler } from '../../utils/response';
|
||||||
|
|
||||||
|
// Basic usage - handles all error types automatically
|
||||||
|
.onError(standardErrorHandler);
|
||||||
|
|
||||||
|
// With custom message for specific errors
|
||||||
|
.onError((e, c) => standardErrorHandler(e, c, 'Custom error message'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Error Utilities
|
||||||
|
- `standardErrorHandler(error, context, customMessage?)` - Complete error handler that returns Hono response for all error types
|
||||||
|
- `handleZodError(zodError)` - Formats Zod validation errors with detailed issues
|
||||||
|
- `errorResponse(message, status)` - Creates HTTPException for throwing errors
|
||||||
|
- `notFoundResponse(resource)` - Standard 404 error
|
||||||
|
- `unauthorizedResponse(message)` - Standard 401 error
|
||||||
|
|
||||||
|
## Required Handler Structure
|
||||||
|
|
||||||
|
### Handler Function Pattern
|
||||||
|
```typescript
|
||||||
|
import { errorResponse } from '../../utils/response';
|
||||||
|
|
||||||
|
async function handlerFunction(
|
||||||
|
resourceId: string,
|
||||||
|
request: RequestType,
|
||||||
|
user: User
|
||||||
|
): Promise<ResponseType> {
|
||||||
|
try {
|
||||||
|
// Database operations through @buster/database only
|
||||||
|
const result = await databaseFunction({ resourceId, ...request });
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
// Log with context
|
||||||
|
console.error('Error in handler:', {
|
||||||
|
resourceId,
|
||||||
|
userId: user.id,
|
||||||
|
error: error instanceof Error ? error.message : error,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-throw Zod errors for route handler
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use shared error response utility
|
||||||
|
throw errorResponse('Operation failed', 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Route Definition Pattern
|
||||||
|
```typescript
|
||||||
|
import { zValidator } from '@hono/zod-validator';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { standardErrorHandler, errorResponse } from '../../utils/response';
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', /* appropriate middleware */)
|
||||||
|
.method('/', zValidator('json', RequestSchema), async (c) => {
|
||||||
|
const request = c.req.valid('json');
|
||||||
|
const user = c.get('busterUser');
|
||||||
|
const userOrg = c.get('userOrganizationInfo');
|
||||||
|
|
||||||
|
const response = await handlerFunction(
|
||||||
|
userOrg.organizationId,
|
||||||
|
request,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response);
|
||||||
|
})
|
||||||
|
.onError(standardErrorHandler);
|
||||||
|
// Or with custom message: .onError((e, c) => standardErrorHandler(e, c, 'Custom message'));
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist for New Routes
|
||||||
|
|
||||||
|
- [ ] Create separate file for each HTTP method (GET.ts, PUT.ts, etc.)
|
||||||
|
- [ ] Define request/response types in `@buster/server-shared`
|
||||||
|
- [ ] Import types with `type` keyword
|
||||||
|
- [ ] Use `zValidator` for request validation
|
||||||
|
- [ ] Apply appropriate middleware (global + method-specific)
|
||||||
|
- [ ] Route database operations through `@buster/database`
|
||||||
|
- [ ] Import and use `standardErrorHandler` from `@/utils/response`
|
||||||
|
- [ ] Implement error handling with `.onError(standardErrorHandler)`
|
||||||
|
- [ ] Use `errorResponse` for throwing consistent errors in handlers
|
||||||
|
- [ ] Combine methods in index.ts with barrel export pattern
|
||||||
|
- [ ] Add global `requireAuth` middleware in index.ts
|
|
@ -90,6 +90,235 @@ Each package in `@/packages` typically contains:
|
||||||
|
|
||||||
This architecture keeps the server layer focused on HTTP routing, middleware, and request/response handling while delegating domain logic to specialized packages.
|
This architecture keeps the server layer focused on HTTP routing, middleware, and request/response handling while delegating domain logic to specialized packages.
|
||||||
|
|
||||||
|
## API Route Patterns
|
||||||
|
|
||||||
|
We follow a specific modular pattern for organizing API routes that promotes maintainability, type safety, and clear separation of concerns.
|
||||||
|
|
||||||
|
### Modular Route Structure
|
||||||
|
|
||||||
|
Each HTTP method is defined in its own dedicated file and exported through a barrel pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/api/v2/organization/
|
||||||
|
├── GET.ts # Handles GET /organization
|
||||||
|
├── PUT.ts # Handles PUT /organization
|
||||||
|
├── POST.ts # Handles POST /organization (if needed)
|
||||||
|
├── DELETE.ts # Handles DELETE /organization (if needed)
|
||||||
|
└── index.ts # Barrel export that combines all methods
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Implementation:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// GET.ts - Individual route handler
|
||||||
|
import { getOrganization } from '@buster/database';
|
||||||
|
import type { GetOrganizationResponse } from '@buster/server-shared/organization';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { requireOrganization } from '../../../middleware/auth';
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', requireOrganization)
|
||||||
|
.get('/', async (c) => {
|
||||||
|
const userOrg = c.get('userOrganizationInfo');
|
||||||
|
|
||||||
|
const organization: GetOrganizationResponse = await getOrganization({
|
||||||
|
organizationId: userOrg.organizationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json(organization);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// index.ts - Barrel export combining all methods
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { requireAuth } from '../../../middleware/auth';
|
||||||
|
import GET from './GET';
|
||||||
|
import PUT from './PUT';
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', requireAuth) // Global middleware for all methods
|
||||||
|
.route('/', GET) // Mount individual route handlers
|
||||||
|
.route('/', PUT);
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Middleware Architecture
|
||||||
|
|
||||||
|
We use a layered middleware approach for authentication and authorization:
|
||||||
|
|
||||||
|
1. **Global Authentication**: Applied at the barrel export level (`requireAuth`)
|
||||||
|
2. **Method-Specific Authorization**: Applied in individual route files (`requireOrganization`, `requireOrganizationAdmin`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// index.ts - Global auth for all methods
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', requireAuth) // ALL routes require authentication
|
||||||
|
.route('/', GET)
|
||||||
|
.route('/', PUT);
|
||||||
|
|
||||||
|
// PUT.ts - Additional admin requirement for updates
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', requireOrganizationAdmin) // PUT requires admin privileges
|
||||||
|
.put('/', zValidator('json', UpdateOrganizationRequestSchema), async (c) => {
|
||||||
|
// Handler logic
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Safety and Validation
|
||||||
|
|
||||||
|
All endpoints must define strict request and response types using our established patterns:
|
||||||
|
|
||||||
|
#### 1. Schema Definition in `@buster/server-shared`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// @buster/server-shared/organization/types.ts
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const UpdateOrganizationRequestSchema = z.object({
|
||||||
|
colorPalette: z.array(z.string()).optional(),
|
||||||
|
// ... other fields
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UpdateOrganizationRequest = z.infer<typeof UpdateOrganizationRequestSchema>;
|
||||||
|
export type UpdateOrganizationResponse = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
// ... organization fields
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Import Types with `type` Keyword
|
||||||
|
|
||||||
|
**Critical**: Always use the `type` keyword when importing types to minimize build size:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type {
|
||||||
|
UpdateOrganizationRequest,
|
||||||
|
UpdateOrganizationResponse
|
||||||
|
} from '@buster/server-shared/organization';
|
||||||
|
import { UpdateOrganizationRequestSchema } from '@buster/server-shared/organization';
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Use Hono's `zValidator` for Request Validation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { zValidator } from '@hono/zod-validator';
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
.put('/', zValidator('json', UpdateOrganizationRequestSchema), async (c) => {
|
||||||
|
const request = c.req.valid('json'); // Fully typed request
|
||||||
|
// Handler logic
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Interaction Pattern
|
||||||
|
|
||||||
|
**Important**: All database interactions must go through the `@buster/database` package. Never interact with the database directly in route handlers.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Correct - Use database package functions
|
||||||
|
import { getOrganization, updateOrganization } from '@buster/database';
|
||||||
|
|
||||||
|
const organization = await getOrganization({ organizationId });
|
||||||
|
await updateOrganization({ organizationId, ...request });
|
||||||
|
|
||||||
|
// ❌ Incorrect - Direct database queries
|
||||||
|
// const result = await db.query('SELECT * FROM organizations...');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling Strategy
|
||||||
|
|
||||||
|
We pass detailed errors straight through to the client to make debugging easier for developers. Use the shared error handling utilities from `@/utils/response`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { standardErrorHandler } from '../../utils/response';
|
||||||
|
|
||||||
|
// Basic usage - handles all error types automatically
|
||||||
|
.onError(standardErrorHandler);
|
||||||
|
|
||||||
|
// With custom message for specific errors
|
||||||
|
.onError((e, c) => standardErrorHandler(e, c, 'Failed to update organization settings'));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available Error Utilities:**
|
||||||
|
- `standardErrorHandler(error, context, customMessage?)` - Complete error handler that returns Hono response for all error types
|
||||||
|
- `handleZodError(zodError)` - Specifically formats Zod validation errors with detailed issues
|
||||||
|
- `errorResponse(message, status)` - Creates HTTPException for throwing errors
|
||||||
|
- `notFoundResponse(resource)` - Standard 404 error
|
||||||
|
- `unauthorizedResponse(message)` - Standard 401 error
|
||||||
|
|
||||||
|
### Complete Route Handler Template
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { /* database functions */ } from '@buster/database';
|
||||||
|
import type { User } from '@buster/database';
|
||||||
|
import type {
|
||||||
|
RequestType,
|
||||||
|
ResponseType
|
||||||
|
} from '@buster/server-shared/feature';
|
||||||
|
import { RequestSchema } from '@buster/server-shared/feature';
|
||||||
|
import { zValidator } from '@hono/zod-validator';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { /* middleware */ } from '../../../middleware/auth';
|
||||||
|
import { standardErrorHandler, errorResponse } from '../../utils/response';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler function with proper error handling
|
||||||
|
*/
|
||||||
|
async function handlerFunction(
|
||||||
|
resourceId: string,
|
||||||
|
request: RequestType,
|
||||||
|
user: User
|
||||||
|
): Promise<ResponseType> {
|
||||||
|
try {
|
||||||
|
// Database operations through @buster/database
|
||||||
|
const result = await databaseFunction({ resourceId, ...request });
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in handler:', {
|
||||||
|
resourceId,
|
||||||
|
userId: user.id,
|
||||||
|
error: error instanceof Error ? error.message : error,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-throw Zod errors for route error handler
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use shared error response utility
|
||||||
|
throw errorResponse('Operation failed', 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
.use('*', /* appropriate middleware */)
|
||||||
|
.put('/', zValidator('json', RequestSchema), async (c) => {
|
||||||
|
const request = c.req.valid('json');
|
||||||
|
const user = c.get('busterUser');
|
||||||
|
const userOrg = c.get('userOrganizationInfo');
|
||||||
|
|
||||||
|
const response = await handlerFunction(
|
||||||
|
userOrg.organizationId,
|
||||||
|
request,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response);
|
||||||
|
})
|
||||||
|
.onError(standardErrorHandler);
|
||||||
|
|
||||||
|
// Or with custom error message for this specific route
|
||||||
|
// .onError((e, c) => standardErrorHandler(e, c, 'Failed to update organization'));
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
```
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
- Use TypeScript for type safety
|
- Use TypeScript for type safety
|
||||||
|
@ -98,3 +327,9 @@ This architecture keeps the server layer focused on HTTP routing, middleware, an
|
||||||
- Use proper error handling with Hono's error utilities
|
- Use proper error handling with Hono's error utilities
|
||||||
- Leverage Hono's built-in validation and serialization
|
- Leverage Hono's built-in validation and serialization
|
||||||
- Follow RESTful conventions for API endpoints
|
- Follow RESTful conventions for API endpoints
|
||||||
|
- **Always use the modular route pattern** with separate files per HTTP method
|
||||||
|
- **Import types with `type` keyword** to minimize build size
|
||||||
|
- **Use `zValidator` for all request validation**
|
||||||
|
- **Route all database interactions through `@buster/database`**
|
||||||
|
- **Use shared error utilities from `@/utils/response`** for consistent error handling
|
||||||
|
- **Pass detailed errors to clients** for easier debugging
|
||||||
|
|
|
@ -7,9 +7,9 @@ import type {
|
||||||
import { UpdateOrganizationRequestSchema } from '@buster/server-shared/organization';
|
import { UpdateOrganizationRequestSchema } from '@buster/server-shared/organization';
|
||||||
import { zValidator } from '@hono/zod-validator';
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { requireOrganizationAdmin } from '../../../middleware/auth';
|
import { requireOrganizationAdmin } from '../../../middleware/auth';
|
||||||
|
import { errorResponse, standardErrorHandler } from '../../../utils/response';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates organization settings
|
* Updates organization settings
|
||||||
|
@ -46,9 +46,7 @@ async function updateOrganizationHandler(
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new HTTPException(500, {
|
throw errorResponse('Failed to update organization', 500);
|
||||||
message: 'Failed to update organization',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,39 +68,6 @@ const app = new Hono()
|
||||||
|
|
||||||
return c.json(response);
|
return c.json(response);
|
||||||
})
|
})
|
||||||
.onError((e, c) => {
|
.onError(standardErrorHandler);
|
||||||
// Handle Zod validation errors with detailed information
|
|
||||||
if (e instanceof z.ZodError) {
|
|
||||||
return c.json(
|
|
||||||
{
|
|
||||||
error: 'Validation Error',
|
|
||||||
message: 'Invalid request data',
|
|
||||||
issues: e.issues.map((issue) => ({
|
|
||||||
path: issue.path.join('.'),
|
|
||||||
message: issue.message,
|
|
||||||
code: issue.code,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
400
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle HTTP exceptions
|
|
||||||
if (e instanceof HTTPException) {
|
|
||||||
return e.getResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log unexpected errors
|
|
||||||
console.error('Unhandled error in organization PUT:', e);
|
|
||||||
|
|
||||||
// Return generic error for unexpected issues
|
|
||||||
return c.json(
|
|
||||||
{
|
|
||||||
error: 'Internal Server Error',
|
|
||||||
message: 'Failed to update organization',
|
|
||||||
},
|
|
||||||
500
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import type { Context } from 'hono';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
|
import { z } from 'zod';
|
||||||
import type { ErrorResponse } from '../types/errors.types';
|
import type { ErrorResponse } from '../types/errors.types';
|
||||||
|
|
||||||
export const errorResponse = (
|
export const errorResponse = (
|
||||||
|
@ -27,3 +29,55 @@ export const unauthorizedResponse = (message = 'Unauthorized') => {
|
||||||
message,
|
message,
|
||||||
} satisfies ErrorResponse);
|
} satisfies ErrorResponse);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles Zod validation errors with detailed issue information
|
||||||
|
* Returns a JSON response with validation error details for easier debugging
|
||||||
|
*/
|
||||||
|
export const handleZodError = (error: z.ZodError) => {
|
||||||
|
return {
|
||||||
|
error: 'Validation Error',
|
||||||
|
message: 'Invalid request data',
|
||||||
|
issues: error.issues.map((issue) => ({
|
||||||
|
path: issue.path.join('.'),
|
||||||
|
message: issue.message,
|
||||||
|
code: issue.code,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard error handler for Hono routes
|
||||||
|
* Handles Zod validation errors, HTTP exceptions, and unexpected errors
|
||||||
|
* Returns complete Hono response with detailed error information for easier debugging
|
||||||
|
*
|
||||||
|
* @param error - The error to handle
|
||||||
|
* @param c - Hono context
|
||||||
|
* @param customMessage - Optional custom message to use instead of default error message
|
||||||
|
*/
|
||||||
|
export const standardErrorHandler = (
|
||||||
|
error: Error | z.ZodError | HTTPException | unknown,
|
||||||
|
c: Context,
|
||||||
|
customMessage?: string
|
||||||
|
) => {
|
||||||
|
// Handle Zod validation errors with detailed information
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
return c.json(handleZodError(error), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle HTTP exceptions - let them manage their own response
|
||||||
|
if (error instanceof HTTPException) {
|
||||||
|
return error.getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log unexpected errors but still return helpful details
|
||||||
|
console.error('Unhandled error:', error);
|
||||||
|
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
error: 'Internal Server Error',
|
||||||
|
message: customMessage || (error instanceof Error ? error.message : 'Unknown error'),
|
||||||
|
},
|
||||||
|
500
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue