npx claudepluginhub kingstinct/.github --plugin typescriptWant just this skill?
Then install: npx claudepluginhub u/[userId]/[slug]
TypeScript best practices for type safety and ergonomic code
This skill uses the workspace's default tool permissions.
TypeScript Guidelines
Write type-safe, ergonomic TypeScript. Prioritize correctness and developer experience.
TSConfig Requirements
Always ensure these compiler options are enabled:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"strictNullChecks": true
}
}
Core Principles
Avoid any
Everything should be typed. If you need escape hatches:
// Bad
const data: any = fetchData();
// Better - use unknown and narrow
const data: unknown = fetchData();
if (isUser(data)) {
console.log(data.name);
}
// Or use generics
function fetchData<T>(): T { ... }
Prefer Existing Types
Reuse types from libraries and codebase. Use Pick/Omit for ergonomics:
// Bad - duplicating types
interface CreateUserInput {
name: string;
email: string;
}
// Good - derive from existing
type CreateUserInput = Pick<User, 'name' | 'email'>;
// Good - omit what you don't need
type UserWithoutId = Omit<User, 'id' | 'createdAt'>;
// Good - for function params
function updateUser(id: string, data: Partial<Pick<User, 'name' | 'email'>>) { ... }
Prefer Interfaces Over Types
// Preferred
interface User {
id: string;
name: string;
}
// Use type for unions, intersections, mapped types
type Status = 'pending' | 'active' | 'inactive';
type UserWithRole = User & { role: Role };
Let TypeScript Infer When Trivial
// Unnecessary - TypeScript infers this
const name: string = 'John';
const count: number = 0;
const users: User[] = [];
// Better - let inference work
const name = 'John';
const count = 0;
const users: User[] = []; // Keep when empty array needs type
Avoid Type Casting When Possible
// Bad - casting
const user = data as User;
// Better - use generics
const user = fetchData<User>();
// Better - type the variable
const user: User = { id: '1', name: 'John' };
// Better - use type guards
if (isUser(data)) {
// data is User here
}
Pragmatic Typing for Isolated Scopes
When a type is complex and isolated, focus on input/output ergonomics:
// Input and output are well-typed, internal casting is acceptable
function transformData(input: RawData): ProcessedData {
const intermediate = input.items.map(item => {
// Complex transformation - casting ok here if isolated
return processItem(item) as ProcessedItem;
});
return { items: intermediate };
}
Type Narrowing
Use TypeScript's narrowing capabilities. Reference: https://www.typescriptlang.org/docs/handbook/2/narrowing.html
Discriminated Unions
interface LoadingState {
status: 'loading';
}
interface SuccessState {
status: 'success';
data: User[];
}
interface ErrorState {
status: 'error';
error: Error;
}
type State = LoadingState | SuccessState | ErrorState;
function render(state: State) {
switch (state.status) {
case 'loading':
return <Spinner />;
case 'success':
return <UserList users={state.data} />;
case 'error':
return <Error message={state.error.message} />;
}
}
instanceof
function handleError(error: unknown) {
if (error instanceof ValidationError) {
return { field: error.field, message: error.message };
}
if (error instanceof Error) {
return { message: error.message };
}
return { message: 'Unknown error' };
}
in Operator
interface Dog { bark(): void; }
interface Cat { meow(): void; }
function speak(animal: Dog | Cat) {
if ('bark' in animal) {
animal.bark();
} else {
animal.meow();
}
}
Type Predicates
Essential for filter functions:
// Type predicate
function isNonNull<T>(value: T | null | undefined): value is T {
return value != null;
}
// Usage with filter
const users: (User | null)[] = [...];
const validUsers: User[] = users.filter(isNonNull);
// Without predicate, TypeScript doesn't narrow
const broken = users.filter(u => u != null); // Still (User | null)[]
Avoid Meaningless Try/Catch
Don't wrap code in try/catch unless you're actually handling the error meaningfully:
// Bad - catching just to rethrow or log
try {
await saveUser(user);
} catch (error) {
console.error(error);
throw error;
}
// Bad - swallowing errors silently
try {
await saveUser(user);
} catch {
// silent failure
}
// Good - let errors propagate naturally
await saveUser(user);
// Good - actual error handling with recovery or transformation
try {
await saveUser(user);
} catch (error) {
if (error instanceof DuplicateEmailError) {
return { success: false, message: 'Email already exists' };
}
throw error; // rethrow unknown errors
}
// Good - cleanup with finally (but consider using `using` instead)
const connection = await getConnection();
try {
await connection.query(sql);
} finally {
await connection.release();
}
Only use try/catch when you:
- Transform the error into a user-facing result
- Recover from specific error types
- Need cleanup logic (prefer
usingkeyword when available) - Are at a boundary where you must not let errors escape
Functional Programming
Prefer functional patterns:
// Bad - imperative loop
const results: string[] = [];
for (const user of users) {
if (user.active) {
results.push(user.name);
}
}
// Good - functional
const results = users
.filter(user => user.active)
.map(user => user.name);
// Good - with type predicate
const activeNames = users
.filter((user): user is ActiveUser => user.active)
.map(user => user.name);
Task Completion Requirements
A task is NOT complete until all type errors are resolved in modified files.
Before marking any task as done:
- Run
bun run typecheck(ortsc --noEmit) - Fix ALL type errors in files you modified
- Verify the typecheck passes cleanly
If the typecheck hook reports errors after your edits, you must fix them before proceeding to the next task.
Quick Reference
| Do | Don't |
|---|---|
interface User | type User = { ... } (for objects) |
Pick<User, 'name'> | Duplicate type definitions |
const x = 5 | const x: number = 5 |
fetchData<User>() | fetchData() as User |
.filter(isNonNull) | .filter(x => x != null) without predicate |
.map().filter() | for loops for transformations |
Similar Skills
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.