.kiro/skills/error-handling/SKILL.md
Structured error handling with AppError in LivestockAI
npx skillsauth add captjay98/gemini-livestockai Error HandlingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
LivestockAI uses a structured AppError class for consistent error handling across the application.
Located in app/lib/errors/app-error.ts:
import { AppError } from '~/lib/errors'
// Throwing errors
throw new AppError('BATCH_NOT_FOUND', {
metadata: { batchId: '123' },
})
throw new AppError('VALIDATION_ERROR', {
message: 'Custom message',
metadata: { field: 'quantity' },
})
throw new AppError('ACCESS_DENIED', {
metadata: { farmId: 'abc' },
})
Common error codes defined in app/lib/errors/error-map.ts:
| Code | HTTP Status | Category | Usage | | ---------------- | ----------- | ---------- | ------------------- | | VALIDATION_ERROR | 400 | validation | Invalid input | | ACCESS_DENIED | 403 | auth | No permission | | BATCH_NOT_FOUND | 404 | not_found | Resource missing | | DATABASE_ERROR | 500 | database | DB operation failed |
class AppError extends Error {
reason: ReasonCode // e.g., 'BATCH_NOT_FOUND'
code: number // Internal code
httpStatus: number // HTTP status code
category: string // Error category
metadata: ErrorMetadata // Additional context
}
export const getBatchFn = createServerFn({ method: 'GET' })
.inputValidator(z.object({ batchId: z.string().uuid() }))
.handler(async ({ data }) => {
const { requireAuth } = await import('../auth/server-middleware')
const session = await requireAuth()
const { getDb } = await import('~/lib/db')
const db = await getDb()
const batch = await getBatchById(db, data.batchId)
if (!batch) {
throw new AppError('BATCH_NOT_FOUND', {
metadata: { batchId: data.batchId },
})
}
const hasAccess = await checkFarmAccess(session.user.id, batch.farmId)
if (!hasAccess) {
throw new AppError('ACCESS_DENIED', {
metadata: { farmId: batch.farmId },
})
}
return batch
})
try {
// Operation
} catch (error) {
if (error instanceof AppError) {
// Re-throw known errors
throw error
}
// Wrap unknown errors
throw new AppError('DATABASE_ERROR', {
message: 'Failed to complete operation',
cause: error,
})
}
if (AppError.isAppError(error)) {
console.log(error.reason) // 'BATCH_NOT_FOUND'
console.log(error.httpStatus) // 404
console.log(error.metadata) // { batchId: '123' }
}
const json = error.toJSON()
// {
// name: 'AppError',
// reason: 'BATCH_NOT_FOUND',
// code: 1001,
// httpStatus: 404,
// category: 'not_found',
// message: 'Batch not found',
// metadata: { batchId: '123' }
// }
const restored = AppError.fromJSON(json)
import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
const mutation = useMutation({
mutationFn: createBatchFn,
onError: (error) => {
if (error.message.includes('ACCESS_DENIED')) {
toast.error('You do not have access to this farm')
} else if (error.message.includes('VALIDATION_ERROR')) {
toast.error('Please check your input')
} else {
toast.error('An error occurred. Please try again.')
}
},
})
three-layer-architecture - Error handling in layerstanstack-start - Server function error handlingdata-ai
Input validation patterns with Zod in LivestockAI server functions
testing
Unit testing patterns with Vitest in LivestockAI
tools
Server → Service → Repository pattern for feature organization
data-ai
Server-side rendering and server functions with TanStack Start in LivestockAI