| name | pinpoint-typescript |
| description | TypeScript strictest patterns, type guards, optional properties (exactOptionalPropertyTypes), Drizzle query safety, null checks. Use when fixing type errors, implementing complex types, or when user mentions TypeScript/types/generics. |
PinPoint TypeScript Guide
When to Use This Skill
Use this skill when:
- Fixing TypeScript compilation errors
- Implementing complex types or generics
- Dealing with optional properties and
exactOptionalPropertyTypes
- Working with Drizzle ORM types
- Handling null/undefined safety
- User mentions: "TypeScript", "type error", "type guard", "optional", "nullable"
Quick Reference
Critical TypeScript Rules
- Strictest config: No
any, no !, no unsafe as
- Explicit return types: Required for public functions
- Path aliases: Use
~/ instead of relative imports
- Optional property assignment: Use conditional spread, not direct assignment with undefined
- Type guards: Write proper predicates for narrowing
Common Fixes
Optional Properties (exactOptionalPropertyTypes):
const data = {
id: uuid(),
...(name && { name }),
...(description && { description }),
};
const data = { name: value };
Detailed Documentation
Read this file for comprehensive TypeScript patterns:
cat docs/TYPESCRIPT_STRICTEST_PATTERNS.md
Core Safety Patterns
Null Safety & Optional Chaining
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user?.id) {
throw new Error("Unauthorized");
}
const userId = user.id;
const firstItem = items[0]?.name ?? "No items";
const lastItem = items.at(-1)?.name ?? "No items";
const machineName = issue.machine?.name ?? "Unknown";
Optional Property Assignment (exactOptionalPropertyTypes)
const data: { name?: string } = {};
if (value) data.name = value;
const data = {
id: uuid(),
...(name && { name }),
...(description && { description }),
};
const data: { name?: string } = { name: value };
Type Guards
function hasItems<T>(arr: T[] | undefined): arr is T[] {
return arr !== undefined && arr.length > 0;
}
if (hasItems(issues)) {
issues.forEach(issue => console.log(issue.title));
}
function isValidUser(user: unknown): user is { id: string; email: string } {
return (
typeof user === "object" &&
user !== null &&
"id" in user &&
"email" in user &&
typeof (user as any).id === "string" &&
typeof (user as any).email === "string"
);
}
type Result =
| { type: "success"; data: string }
| { type: "error"; message: string };
function processResult(result: Result) {
if (result.type === "success") {
console.log(result.data);
} else {
console.log(result.message);
}
}
Common Error Fixes
"Object is possibly null" Errors
const user = await getUserById(id);
console.log(user.email);
const user = await getUserById(id);
if (!user) throw new Error("User not found");
console.log(user.email);
const user = await getUserById(id);
console.log(user?.email ?? "No email");
"Argument of type X is not assignable to parameter" Errors
const id: string | number = getId();
const result = await fetchUser(id);
const id: string | number = getId();
const userId = typeof id === "string" ? id : String(id);
const result = await fetchUser(userId);
Union Type Errors
type Result =
| { type: "success"; data: string }
| { type: "error"; message: string };
function processResult(result: Result) {
if (result.type === "success") {
console.log(result.data);
} else {
console.log(result.message);
}
}
Drizzle Query Safety
Safe Query Patterns
import { eq, and, desc } from "drizzle-orm";
import { issues, machines } from "~/server/db/schema";
export async function getIssuesForMachine(machineId: string): Promise<Issue[]> {
if (!machineId) {
throw new Error("Machine ID required");
}
return await db.query.issues.findMany({
where: eq(issues.machineId, machineId),
orderBy: desc(issues.createdAt),
});
}
const users = await db.query.users.findMany({
columns: {
id: true,
email: true,
name: true,
},
});
const issuesWithMachines = await db.query.issues.findMany({
with: {
machine: true,
},
});
Database Type Conversion
import { users } from "~/server/db/schema";
type DbUser = typeof users.$inferSelect;
export type User = {
id: string;
email: string;
fullName: string | null;
createdAt: Date;
};
export function dbUserToUser(dbUser: DbUser): User {
return {
id: dbUser.id,
email: dbUser.email,
fullName: dbUser.full_name,
createdAt: new Date(dbUser.created_at),
};
}
Supabase SSR Safety
Safe Auth Context
export default async function ProtectedPage() {
const supabase = await createClient();
const {
data: { user },
error,
} = await supabase.auth.getUser();
if (error || !user) {
redirect("/login");
}
return <DashboardContent user={user} />;
}
Server Action Safety
"use server";
export async function updateProfile(formData: FormData): Promise<void> {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
redirect("/login");
}
const name = formData.get("name");
if (typeof name !== "string") {
throw new Error("Name must be a string");
}
await db.update(users).set({ name }).where(eq(users.id, user.id));
revalidatePath("/profile");
}
Strict Function Signatures
export async function getIssuesForMachine(machineId: string): Promise<Issue[]> {
if (!machineId) {
throw new Error("Machine ID required");
}
return await db.query.issues.findMany({
where: eq(issues.machineId, machineId),
});
}
export function createIssue(data: {
title: string;
description?: string;
machineId: string;
severity: "minor" | "playable" | "unplayable";
}): Promise<Issue> {
return db.insert(issues).values(data).returning();
}
Import Path Consistency
import { validateUser } from "~/lib/validation/user";
import { createClient } from "~/lib/supabase/server";
import { validateUser } from "../../../lib/validation/user";
Anti-Patterns to Avoid
const data: any = await fetchData();
const user = getUser()!.email;
const result = dangerousOperation();
const user = data as User;
function isUser(data: unknown): data is User {
return typeof data === "object" && data !== null && "id" in data;
}
if (isUser(data)) {
const user = data;
}
Server Components Safety
Async Component Patterns
export default async function MachineIssuesPage({
params
}: {
params: { machineId: string }
}): Promise<JSX.Element> {
if (!params.machineId) {
throw new Error("Machine ID required");
}
const issues = await db.query.issues.findMany({
where: eq(issues.machineId, params.machineId),
orderBy: desc(issues.createdAt),
});
return (
<div>
{issues.map((issue) => (
<IssueCard key={issue.id} issue={issue} />
))}
</div>
);
}
Form Data Validation
"use server";
import { z } from "zod";
const createIssueSchema = z.object({
title: z.string().min(1, "Title required"),
description: z.string().optional(),
machineId: z.string().uuid("Invalid machine ID"),
severity: z.enum(["minor", "playable", "unplayable"]),
});
export async function createIssueAction(formData: FormData) {
const rawData = {
title: formData.get("title"),
description: formData.get("description"),
machineId: formData.get("machineId"),
severity: formData.get("severity"),
};
const validData = createIssueSchema.parse(rawData);
const [issue] = await db.insert(issues).values(validData).returning();
revalidatePath("/issues");
return issue;
}
TypeScript Checklist
Before committing TypeScript code:
Additional Resources
- TypeScript patterns:
docs/TYPESCRIPT_STRICTEST_PATTERNS.md
- Non-negotiables:
docs/NON_NEGOTIABLES.md (CORE-TS-* rules)
- Drizzle types: Use Context7 MCP for latest patterns