This skill provides TypeScript best practices and conventions for the fitness app. Use when writing TypeScript code, defining types, working with interfaces, or ensuring type safety.
/plugin marketplace add JosephAnson/claude-plugin/plugin install josephanson-ja@JosephAnson/claude-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill provides strict TypeScript conventions for the fitness application.
Strict TypeScript: All code must be strictly typed. The project uses strict mode with no exceptions.
Prefer Interfaces: Use interfaces over type aliases for better extendability and declaration merging.
No Enums: Avoid TypeScript enums. Use const objects (maps) for better type safety and flexibility.
1. Explicit typing for all code:
// ✅ Correct: Explicitly typed
function calculateBMI(weight: number, height: number): number {
return weight / (height * height)
}
// ✅ Correct: Typed parameters
const users: User[] = []
const count: number = users.length
2. Type component props and emits:
<script setup lang="ts">
interface Props {
workout: Workout
isActive?: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
update: [workout: Workout]
delete: [id: string]
}>()
</script>
3. Use unknown instead of any:
// ✅ Correct: Use unknown for type-safe handling
function processData(data: unknown) {
if (typeof data === 'string') {
return data.toUpperCase()
}
throw new Error('Invalid data type')
}
// ❌ Wrong: Using any
function processData(data: any) {
return data.toUpperCase() // Unsafe
}
4. Implement type guards:
interface Workout {
id: string
name: string
}
function isWorkout(value: unknown): value is Workout {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
typeof (value as Workout).id === 'string' &&
typeof (value as Workout).name === 'string'
)
}
// Usage
function processData(data: unknown) {
if (isWorkout(data)) {
// TypeScript knows data is Workout here
console.log(data.name)
}
}
1. Never use any:
// ❌ Wrong
function process(data: any) {
return data.property // Unsafe
}
// ✅ Correct
function process(data: unknown) {
if (isValidData(data)) {
return data.property // Type-safe
}
}
2. Never use @ts-ignore:
// ❌ Wrong: Ignoring TypeScript errors
// @ts-ignore
const value = dangerousOperation()
// ✅ Correct: Fix the underlying issue or use proper type assertion
const value = dangerousOperation() as ExpectedType
3. Never use TypeScript enums:
// ❌ Wrong: TypeScript enum
enum WorkoutType {
Strength = 'strength',
Cardio = 'cardio',
}
// ✅ Correct: Const object
const WORKOUT_TYPE = {
STRENGTH: 'strength',
CARDIO: 'cardio',
} as const
type WorkoutType = typeof WORKOUT_TYPE[keyof typeof WORKOUT_TYPE]
Interfaces are preferred for object shapes because they:
// ✅ Correct: Interface for object shapes
interface User {
id: string
name: string
email: string
}
interface AdminUser extends User {
permissions: string[]
}
// Declaration merging works with interfaces
interface User {
createdAt: Date
}
Use type aliases for:
// ✅ Correct: Type for unions
type Status = 'active' | 'inactive' | 'pending'
// ✅ Correct: Type for intersections
type TimestampedUser = User & {
createdAt: Date
updatedAt: Date
}
// ✅ Correct: Mapped type
type ReadonlyUser = {
readonly [K in keyof User]: User[K]
}
Derive all types from Drizzle DB schemas:
// ✅ Correct: Derive from DB schema
import { type InferSelectModel, type InferInsertModel } from 'drizzle-orm'
import { workouts } from '~~/server/database/schema/workouts'
type Workout = InferSelectModel<typeof workouts>
type InsertWorkout = InferInsertModel<typeof workouts>
// ✅ Correct: Extend DB types for specific use cases
interface WorkoutWithExercises extends Workout {
exercises: Exercise[]
}
// ❌ Wrong: Manually defining types that mirror DB schemas
interface Workout {
id: string
name: string
createdAt: Date
// ... duplicating schema
}
Use proper, specific error types:
// ✅ Correct: Specific error types
class NotFoundError extends Error {
statusCode = 404
constructor(message: string) {
super(message)
this.name = 'NotFoundError'
}
}
class ValidationError extends Error {
statusCode = 400
constructor(
message: string,
public field: string
) {
super(message)
this.name = 'ValidationError'
}
}
// Usage
function getWorkout(id: string): Workout {
const workout = findWorkout(id)
if (!workout) {
throw new NotFoundError(`Workout ${id} not found`)
}
return workout
}
Use as const for immutable values:
// ✅ Correct: Const assertion for literal types
const WORKOUT_TYPES = {
STRENGTH: 'strength',
CARDIO: 'cardio',
FLEXIBILITY: 'flexibility',
} as const
type WorkoutType = typeof WORKOUT_TYPES[keyof typeof WORKOUT_TYPES]
// Type: 'strength' | 'cardio' | 'flexibility'
// ✅ Correct: Const assertion for arrays
const DIFFICULTY_LEVELS = ['beginner', 'intermediate', 'advanced'] as const
type DifficultyLevel = typeof DIFFICULTY_LEVELS[number]
// Type: 'beginner' | 'intermediate' | 'advanced'
Use generics for reusable, type-safe functions:
// ✅ Correct: Generic type for API responses
interface ApiResponse<T> {
data: T
meta: {
page: number
total: number
}
}
function fetchList<T>(endpoint: string): Promise<ApiResponse<T>> {
return $fetch<ApiResponse<T>>(endpoint)
}
// Usage with type inference
const workouts = await fetchList<Workout>('/api/workouts')
// workouts.data is Workout[]
Leverage TypeScript utility types:
// Partial - make all properties optional
type UpdateWorkout = Partial<Workout>
// Pick - select specific properties
type WorkoutSummary = Pick<Workout, 'id' | 'name' | 'createdAt'>
// Omit - exclude specific properties
type WorkoutWithoutTimestamps = Omit<Workout, 'createdAt' | 'updatedAt'>
// Required - make all properties required
type RequiredUser = Required<User>
// Record - create object type with specific keys
type WorkoutMap = Record<string, Workout>
// ReturnType - extract return type of function
function getWorkout() {
return { id: '1', name: 'Test' }
}
type WorkoutReturn = ReturnType<typeof getWorkout>
Use discriminated unions for type-safe state handling:
// ✅ Correct: Discriminated union
type LoadingState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
function renderWorkout(state: LoadingState<Workout>) {
switch (state.status) {
case 'idle':
return 'Not loaded'
case 'loading':
return 'Loading...'
case 'success':
return state.data.name // TypeScript knows data exists
case 'error':
return state.error.message // TypeScript knows error exists
}
}
Use Zod for runtime validation with TypeScript inference:
import { z } from 'zod'
// ✅ Correct: Define Zod schema
const createWorkoutSchema = z.object({
name: z.string().min(1).max(100),
description: z.string().max(500).optional(),
exercises: z.array(z.uuid()).min(1),
})
// Infer TypeScript type from Zod schema
type CreateWorkout = z.infer<typeof createWorkoutSchema>
// Use in validation
function validateWorkout(data: unknown): CreateWorkout {
return createWorkoutSchema.parse(data)
}
Type function parameters and return values:
// ✅ Correct: Explicitly typed function
function calculateVolume(
sets: number,
reps: number,
weight: number
): number {
return sets * reps * weight
}
// ✅ Correct: Typed arrow function
const calculateCalories = (
duration: number,
intensity: number
): number => {
return duration * intensity * 3.5
}
// ✅ Correct: Function type
type MathOperation = (a: number, b: number) => number
const add: MathOperation = (a, b) => a + b
const subtract: MathOperation = (a, b) => a - b
Always type async functions:
// ✅ Correct: Typed async function
async function fetchWorkout(id: string): Promise<Workout> {
const workout = await $fetch<Workout>(`/api/workouts/${id}`)
return workout
}
// ✅ Correct: Async function with error handling
async function createWorkout(
data: InsertWorkout
): Promise<Workout | null> {
try {
return await $fetch<Workout>('/api/workouts', {
method: 'POST',
body: data
})
} catch {
return null
}
}
Use branded types for type-safe IDs:
// ✅ Correct: Branded type for IDs
type WorkoutId = string & { readonly __brand: 'WorkoutId' }
type UserId = string & { readonly __brand: 'UserId' }
function createWorkoutId(id: string): WorkoutId {
return id as WorkoutId
}
function getWorkout(id: WorkoutId): Workout {
// ...
}
// Type safety prevents mixing IDs
const workoutId = createWorkoutId('workout-123')
const userId = createUserId('user-456')
getWorkout(workoutId) // ✅ OK
getWorkout(userId) // ❌ Type error
Use type narrowing for safe type handling:
function processValue(value: string | number | null) {
// Type narrowing with typeof
if (typeof value === 'string') {
return value.toUpperCase() // value is string
}
if (typeof value === 'number') {
return value.toFixed(2) // value is number
}
// value is null here
return 'No value'
}
// Type narrowing with in operator
interface Workout {
name: string
}
interface Exercise {
title: string
}
function getName(item: Workout | Exercise): string {
if ('name' in item) {
return item.name // item is Workout
}
return item.title // item is Exercise
}
unknown instead of anyany@ts-ignoreInferSelectModel and InferInsertModelFor advanced patterns:
references/advanced-types.md - Advanced TypeScript patternsreferences/zod-integration.md - Zod and TypeScript integrationActivates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Activates when the user asks about Agent Skills, wants to find reusable AI capabilities, needs to install skills, or mentions skills for Claude. Use for discovering, retrieving, and installing skills.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.