
typescript
by JosephAnson
SKILL.md
name: typescript description: 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.
TypeScript Best Practices
This skill provides strict TypeScript conventions for the fitness application.
Core Principles
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.
Type Safety Rules
✅ MUST DO
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)
}
}
❌ NEVER DO
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 vs Types
Prefer Interfaces
Interfaces are preferred for object shapes because they:
- Support declaration merging
- Provide better error messages
- Are more extensible
// ✅ 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
}
When to Use Types
Use type aliases for:
- Unions
- Intersections
- Primitives
- Mapped types
- Conditional types
// ✅ 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]
}
Database-First Types
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
}
Error Types
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
}
Const Assertions
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'
Generic Types
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[]
Utility Types
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>
Discriminated Unions
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
}
}
Zod Integration
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)
}
Function Types
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
Async Function Types
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
}
}
Branded Types
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
Type Narrowing
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
}
Summary: TypeScript Rules
Type Safety
- ✅ Use strict TypeScript everywhere
- ✅ Prefer interfaces for object shapes
- ✅ Use type aliases for unions and intersections
- ✅ Implement type guards for runtime validation
- ✅ Use
unknowninstead ofany - ❌ Never use
any - ❌ Never use
@ts-ignore - ❌ Never use TypeScript enums
Database Types
- ✅ Derive types from Drizzle schemas
- ✅ Use
InferSelectModelandInferInsertModel - ❌ Don't manually define types that mirror DB schemas
Best Practices
- ✅ Type all function parameters and returns
- ✅ Type component props and emits
- ✅ Use const assertions for literal types
- ✅ Leverage utility types (Partial, Pick, Omit, etc.)
- ✅ Use discriminated unions for state
- ✅ Integrate Zod for runtime validation
Reference Files
For advanced patterns:
references/advanced-types.md- Advanced TypeScript patternsreferences/zod-integration.md- Zod and TypeScript integration
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です