| name | typescript-expert |
| description | Audit TypeScript code for type safety issues, anti-patterns, and duplicate type definitions. Use when fixing type errors, reviewing TypeScript code, auditing for "any" or "as" usage, checking for duplicate types, or ensuring Supabase types are used as source of truth. |
TypeScript Expert
Enforce strict typing, eliminate convenience anti-patterns, and ensure database types flow correctly from Supabase through to the frontend.
Core Principle
Supabase types are the source of truth. All entity types must derive from database.types.ts → use DbXxx aliases from db.ts → convert to AppXxx for frontend when needed.
Type Flow
database.types.ts (auto-generated)
↓
db.ts (DbUser, DbProfile, etc.)
↓
AppUser, AppProfile (camelCase for frontend)
Never create standalone entity interfaces that don't derive from database.types.ts.
Audit Checklist
When reviewing TypeScript code, check for these issues in order of severity:
Critical (Must Fix)
High (Should Fix)
Medium (Consider Fixing)
Quick Audit Commands
Run these to find common issues:
rg " as [A-Z]" --type ts --type tsx -g '!*.d.ts' web/src/
rg ": any" --type ts --type tsx web/src/
rg "^(interface|type) " --type ts --type tsx web/src/ -o | sort | uniq -d
rg "^interface (User|Profile|Event|Match|Conversation)" --type ts --type tsx web/src/
Correct Patterns
Database → Frontend Type Flow
import type { DbUser, DbProfile, AppUser, dbUserToApp } from "@/types/db";
const { data } = await supabase.from("users").select("*");
const user: DbUser = data;
return NextResponse.json({ user: dbUserToApp(user) });
interface User {
id: string;
email: string;
}
External Data Validation
import { z } from "zod";
const WebhookSchema = z.object({
event: z.enum(["created", "updated", "deleted"]),
data: z.record(z.unknown()),
});
export async function POST(request: Request) {
const body = await request.json();
const payload = WebhookSchema.parse(body);
}
export async function POST(request: Request) {
const payload = (await request.json()) as WebhookPayload;
}
Exhaustive Switch
type Status = "active" | "pending" | "completed";
function getLabel(status: Status): string {
switch (status) {
case "active":
return "Active";
case "pending":
return "Pending";
case "completed":
return "Completed";
default:
const _exhaustive: never = status;
throw new Error(`Unhandled status: ${status}`);
}
}
Explicit State Types
interface DraftOrder {
items: Item[];
}
interface SubmittedOrder {
id: string;
userId: string;
items: Item[];
submittedAt: string;
}
type Order = DraftOrder | SubmittedOrder;
interface Order {
id?: string;
userId?: string;
items?: Item[];
submittedAt?: string;
}
Project-Specific Issues
Known Duplicate Types in This Codebase
The web/src/types/index.ts file contains manually-defined types that duplicate the Supabase-generated types:
| Manual Type (index.ts) | Should Use (db.ts) |
|---|
User | DbUser, AppUser |
Profile | DbProfile, AppProfile |
UserGallery | DbUserGallery, AppUserGallery |
Event | DbEvent, AppEvent |
Notification | DbNotification, AppNotification |
Action: When you encounter code using types from index.ts, migrate to use db.ts types.
Constants Are Fine
The *_OPTIONS arrays in index.ts (like GENDER_OPTIONS, BODY_TYPE_OPTIONS) are fine—these are UI constants, not database types.
Fixing Workflow
- Identify the issue using audit commands
- Determine the correct type source:
- Entity data →
db.ts types
- API request body → Zod schema validation
- Component props → Define locally or in component file
- Update imports to use correct types
- Add runtime validation at system boundaries (API routes, webhooks, form handlers)
- Test that the type flows correctly end-to-end
Additional Resources
For detailed anti-pattern examples, see anti-patterns-reference.md.