Type Safety
Improve TypeScript type safety by eliminating unsafe type assertions and using proper types from the source.
CRITICAL: Drizzle Relation Type Inference Failure
This is a recurring bug in WODsmith. Drizzle's with: { relation: true } clause does NOT automatically include relation types in the return type. This causes:
error TS2339: Property 'workout' does not exist on type '{ id: string; trackId: string; ... }'
error TS2551: Property 'track' does not exist on type '{ ... }'. Did you mean 'trackId'?
Root Cause
- •Query uses
db.query.table.findFirst({ with: { workout: true } }) - •Runtime: Returns object with
workoutproperty populated - •Compile time: TypeScript only sees base table columns, not relations
- •Result: Type errors when accessing
.workout,.track,.competition, etc.
Three Fix Strategies (Choose Based on Context)
Strategy 1: Type Alias with Cast (Simple, for single file use)
import { type TrackWorkout, type Workout } from "@/db/schema"
type TrackWorkoutWithWorkout = TrackWorkout & { workout: Workout }
const result = await db.query.trackWorkoutsTable.findFirst({
where: eq(trackWorkoutsTable.id, id),
with: { workout: true }
}) as TrackWorkoutWithWorkout | undefined
Strategy 2: Explicit Join (Most Type-Safe, for complex queries)
When relation chains are deep or you need specific fields:
// Instead of .with() which has type inference issues:
const result = await db
.select({
id: trackWorkoutsTable.id,
trackId: trackWorkoutsTable.trackId,
// ... other trackWorkout fields
workout: workoutsTable, // Full workout object
// OR select specific fields:
workoutName: workoutsTable.name,
workoutScheme: workoutsTable.scheme,
})
.from(trackWorkoutsTable)
.innerJoin(workoutsTable, eq(trackWorkoutsTable.workoutId, workoutsTable.id))
.where(eq(trackWorkoutsTable.id, id))
Strategy 3: Type Guard for Optional Relations
// When relation might not be loaded
if (result && 'workout' in result && result.workout) {
const workout = result.workout as Workout
// Use workout safely
}
// Or with type guard function
function hasWorkout(tw: TrackWorkout): tw is TrackWorkout & { workout: Workout } {
return 'workout' in tw && tw.workout !== undefined
}
Common WODsmith Type Error Patterns
Pattern 1: Missing .workout on trackWorkout
Error: Property 'workout' does not exist on type '{ createdAt: Date; ... workoutId: string; }'
Files affected: competition-scores.ts, competition-workouts.ts, programming.ts
Fix: Use Strategy 1 or 2 above.
Pattern 2: Missing .track or .competition
Error: Property 'track' does not exist... Did you mean 'trackId'?
Fix: Same strategies. The relation IS loaded, TypeScript just doesn't know.
Pattern 3: Missing New Schema Columns
Error: Property 'minHeatBuffer' is missing in type
Cause: Schema changed, new columns added, but code constructing objects manually doesn't include them.
Fix: Add the missing properties with appropriate defaults (usually null):
const tempWorkout = {
...existingFields,
defaultHeatsCount: null,
defaultLaneShiftPattern: null,
minHeatBuffer: null, // New field!
}
Pattern 4: Possibly Undefined Array Access
Error: 'rot1' is possibly 'undefined'
Fix: Add null checks before accessing:
// Bad
const rot1 = rotations.find(r => r.id === id1)
const rot2 = rotations.find(r => r.id === id2)
console.log(rot1.startingHeat) // Error!
// Good
if (!rot1 || !rot2) {
continue // or throw, or return early
}
console.log(rot1.startingHeat) // Safe
Prevention Checklist
When writing queries with relations:
- • Check if return type includes relation properties
- • If not, create type alias:
type XWithY = X & { y: Y } - • Apply cast at query site:
as XWithY | undefined - • For complex joins, prefer explicit
.select().from().innerJoin() - • When constructing objects manually, check schema for ALL current columns
References
For detailed examples and patterns, see:
- •references/drizzle-patterns.md - Common Drizzle ORM type patterns
- •references/examples.md - Real examples from the codebase