Use when you have a written implementation plan to execute in a separate session with review checkpoints
npx skills add 333-333-333/agents --skill "typescript"
Install specific skill from multi-skill repository
# Description
>
# SKILL.md
name: typescript
description: >
TypeScript strict patterns and best practices.
Trigger: When implementing or refactoring TypeScript in .ts/.tsx (types, interfaces, generics, const maps, type guards, removing any, tightening unknown).
license: Apache-2.0
metadata:
author: 333-333-333
version: "1.0"
type: generic
scope: [root]
auto_invoke:
- "Writing TypeScript types or interfaces"
- "Refactoring to remove any types"
- "Creating type guards or generics"
Critical Rules
- Const types pattern - ALWAYS const object first, then extract type
- Flat interfaces - Max one level deep, nested β dedicated interface
- No
any- Useunknown+ type guards or generics - Import types - Use
import typefor type-only imports
Const Types Pattern (REQUIRED)
// β
ALWAYS: Create const object first, then extract type
const STATUS = {
ACTIVE: "active",
INACTIVE: "inactive",
PENDING: "pending",
} as const;
type Status = (typeof STATUS)[keyof typeof STATUS];
// Result: "active" | "inactive" | "pending"
// β
Use the const for runtime, type for annotations
function setStatus(status: Status) {
if (status === STATUS.ACTIVE) { /* ... */ }
}
// β NEVER: Direct union types (no runtime values)
type Status = "active" | "inactive" | "pending";
Why? Single source of truth, runtime values, autocomplete, easier refactoring.
Const Pattern with Labels
const HTTP_STATUS = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
NOT_FOUND: 404,
} as const;
type HttpStatus = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS];
// Result: 200 | 201 | 400 | 404
// With display labels
const STATUS_LABELS: Record<Status, string> = {
[STATUS.ACTIVE]: "Active",
[STATUS.INACTIVE]: "Inactive",
[STATUS.PENDING]: "Pending",
};
Flat Interfaces (REQUIRED)
// β
ALWAYS: One level depth, nested objects β dedicated interface
interface UserAddress {
street: string;
city: string;
zipCode: string;
}
interface User {
id: string;
name: string;
email: string;
address: UserAddress; // Reference, not inline
}
// β
Extend for variations
interface Admin extends User {
permissions: string[];
role: "admin";
}
interface Guest extends Pick<User, "id" | "name"> {
sessionId: string;
}
// β NEVER: Inline nested objects
interface User {
address: { street: string; city: string }; // NO!
}
// β NEVER: Deep nesting
interface User {
profile: {
settings: {
notifications: { email: boolean }; // NO!
};
};
}
Never Use any
// β
Use unknown for truly unknown types
function parseJSON(input: string): unknown {
return JSON.parse(input);
}
// β
Narrow with type guards
function processUser(input: unknown): User {
if (isUser(input)) return input;
throw new Error("Invalid user data");
}
// β
Use generics for flexible types
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// β
For event handlers, type the event
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
// ...
}
// β NEVER use any
function parse(input: any): any { } // NO!
const data: any = fetchData(); // NO!
Dealing with Third-Party any
// β
Wrap and type external APIs
interface ExternalApiResponse {
data: User[];
meta: { total: number };
}
async function fetchUsers(): Promise<ExternalApiResponse> {
const response = await externalApi.get("/users");
return response as ExternalApiResponse; // Type assertion at boundary
}
// β
Use type assertion only at boundaries, validate if critical
function fromExternalApi(data: unknown): User {
if (!isUser(data)) {
throw new TypeError("Invalid user from API");
}
return data;
}
Type Guards
// β
Basic type guard
function isString(value: unknown): value is string {
return typeof value === "string";
}
// β
Object type guard
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
typeof (value as User).id === "string" &&
typeof (value as User).name === "string"
);
}
// β
Array type guard
function isUserArray(value: unknown): value is User[] {
return Array.isArray(value) && value.every(isUser);
}
// β
Discriminated union guard
interface Success { status: "success"; data: User }
interface Error { status: "error"; message: string }
type Result = Success | Error;
function isSuccess(result: Result): result is Success {
return result.status === "success";
}
Assertion Functions
// β
Assert and throw if invalid
function assertIsUser(value: unknown): asserts value is User {
if (!isUser(value)) {
throw new TypeError("Expected User");
}
}
// Usage - type is narrowed after assertion
function process(input: unknown) {
assertIsUser(input);
console.log(input.name); // TypeScript knows it's User
}
Utility Types
Selection & Exclusion
// Pick specific fields
type UserPreview = Pick<User, "id" | "name">;
// Omit specific fields
type UserWithoutId = Omit<User, "id">;
// Combine for API payloads
type CreateUserPayload = Omit<User, "id" | "createdAt">;
type UpdateUserPayload = Partial<Omit<User, "id">>;
Modifiers
// All fields optional
type PartialUser = Partial<User>;
// All fields required
type RequiredUser = Required<User>;
// All fields readonly
type ReadonlyUser = Readonly<User>;
// Deep readonly (custom)
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
Object Types
// Record for dictionaries
type UserById = Record<string, User>;
type StatusCount = Record<Status, number>;
// Index signatures
interface Cache {
[key: string]: User | undefined; // Always include undefined
}
Union Manipulation
type Status = "active" | "inactive" | "pending" | "deleted";
// Extract subset
type ActiveStatus = Extract<Status, "active" | "pending">;
// Result: "active" | "pending"
// Exclude subset
type VisibleStatus = Exclude<Status, "deleted">;
// Result: "active" | "inactive" | "pending"
// Remove null/undefined
type NonNullUser = NonNullable<User | null | undefined>;
Function Types
function createUser(name: string, email: string): User {
return { id: crypto.randomUUID(), name, email };
}
// Extract return type
type CreateUserReturn = ReturnType<typeof createUser>;
// Result: User
// Extract parameters
type CreateUserParams = Parameters<typeof createUser>;
// Result: [string, string]
// First parameter
type FirstParam = Parameters<typeof createUser>[0];
// Result: string
Generics
Basic Generics
// β
Generic function
function identity<T>(value: T): T {
return value;
}
// β
Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// β
Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Usage
type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;
Generic Constraints
// Must have id property
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
// Must be object
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
Default Generic Types
interface PaginatedResponse<T, M = { total: number }> {
data: T[];
meta: M;
}
// Uses default meta
type UserPage = PaginatedResponse<User>;
// Custom meta
type UserPageWithCursor = PaginatedResponse<User, { cursor: string }>;
Discriminated Unions
// β
Use literal type as discriminant
interface LoadingState {
status: "loading";
}
interface SuccessState<T> {
status: "success";
data: T;
}
interface ErrorState {
status: "error";
error: Error;
}
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
// β
Exhaustive handling
function handleState<T>(state: AsyncState<T>): string {
switch (state.status) {
case "loading":
return "Loading...";
case "success":
return `Got ${state.data}`;
case "error":
return `Error: ${state.error.message}`;
default:
// Exhaustiveness check
const _exhaustive: never = state;
return _exhaustive;
}
}
Import Types
// β
Type-only imports (removed at compile time)
import type { User, Status } from "./types";
// β
Mixed imports
import { createUser, type Config } from "./utils";
// β
Re-export types
export type { User, Status } from "./types";
export { createUser } from "./utils";
File Organization
src/
βββ types/
β βββ index.ts # Re-exports all types
β βββ user.ts # User-related types
β βββ api.ts # API response types
β βββ constants.ts # Const maps and derived types
βββ utils/
β βββ guards.ts # Type guard functions
βββ features/
βββ users/
βββ types.ts # Feature-specific types
types/index.ts
// Re-export all public types
export type { User, Admin, Guest } from "./user";
export type { ApiResponse, PaginatedResponse } from "./api";
export { STATUS, HTTP_STATUS } from "./constants";
export type { Status, HttpStatus } from "./constants";
tsconfig.json Recommendations
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true
}
}
| Option | Why |
|---|---|
strict |
Enables all strict checks |
noUncheckedIndexedAccess |
arr[0] returns T \| undefined |
exactOptionalPropertyTypes |
{ x?: string } doesn't accept undefined |
verbatimModuleSyntax |
Enforces import type syntax |
Quick Reference
| Pattern | Use |
|---|---|
const X = {} as const |
Enum-like values with types |
(typeof X)[keyof typeof X] |
Extract union from const |
interface A extends B |
Inheritance |
Pick<T, K> |
Select fields |
Omit<T, K> |
Exclude fields |
Partial<T> |
All optional |
Record<K, V> |
Object/dictionary type |
value is Type |
Type guard return |
asserts value is Type |
Assertion function |
T extends Constraint |
Generic constraint |
Resources
- Templates: See assets/ for tsconfig and type examples
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.