/typescript-patterns
TypeScript types: generics, discriminated unions, Zod, satisfies, branded types. Triggers: TypeScript, TS, generics, Zod, satisfies, strict mode.
$ npx -y skills add softspark/ai-toolkit --skill typescript-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/typescript-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
TypeScript types: generics, discriminated unions, Zod, satisfies, branded types. Triggers: TypeScript, TS, generics, Zod, satisfies, strict mode.
SKILL.md
typescript-patterns.SKILL.mdname: typescript-patterns
description: "TypeScript types: generics, discriminated unions, Zod, satisfies, branded types. Triggers: TypeScript, TS, generics, Zod, satisfies, strict mode."
effort: medium
user-invocable: false
allowed-tools: Read
TypeScript/JavaScript Patterns
TypeScript Configuration
Strict tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ES2022",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}ESLint + Prettier
// .eslintrc.json (flat config recommended for new projects)
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended-type-checked",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error"
}
}React Patterns
Functional Component
interface UserCardProps {
user: User;
onSelect?: (id: string) => void;
}
export function UserCard({ user, onSelect }: UserCardProps) {
const handleClick = useCallback(() => {
onSelect?.(user.id);
}, [user.id, onSelect]);
return (
<div onClick={handleClick} role="button" tabIndex={0}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}Custom Hook
function useAsync<T>(fn: () => Promise<T>, deps: unknown[]) {
const [state, setState] = useState<{
data: T | null;
error: Error | null;
loading: boolean;
}>({ data: null, error: null, loading: true });
useEffect(() => {
let cancelled = false;
setState(prev => ({ ...prev, loading: true }));
fn()
.then(data => { if (!cancelled) setState({ data, error: null, loading: false }); })
.catch(error => { if (!cancelled) setState({ data: null, error, loading: false }); });
return () => { cancelled = true; };
}, deps);
return state;
}Context Pattern
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error("useAuth must be within AuthProvider");
return context;
}Next.js App Router
Server Component (default)
// app/users/page.tsx
export default async function UsersPage() {
const users = await db.user.findMany();
return <UserList users={users} />;
}Server Action
"use server";
import { revalidatePath } from "next/cache";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
await db.user.create({ data: { name } });
revalidatePath("/users");
}Node.js/Express Patterns
Type-safe Route Handler
import { Router, Request, Response } from "express";
import { z } from "zod";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
});
router.post("/users", async (req: Request, res: Response) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
const user = await userService.create(result.data);
res.status(201).json(user);
});State Management
Zustand Store
import { create } from "zustand";
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
total: () => number;
}
export const useCartStore = create<CartStore>((set, get) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
removeItem: (id) => set((state) => ({ items: state.items.filter(i => i.id !== id) })),
total: () => get().items.reduce((sum, item) => sum + item.price, 0),
}));Type-safe API (tRPC / Zod)
Zod Schema Validation
import { z } from "zod";
export const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["admin", "user", "guest"]),
createdAt: z.coerce.date(),
});
export type User = z.infer<typeof UserSchema>;Error Handling
class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code: string = "INTERNAL_ERROR"
) {
super(message);
this.name = "AppError";
}
}
// Result pattern (no exceptions)
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function parseUser(raw: unknown): Result<User> {
const result = UserSchema.safeParse(raw);
if (result.success) return { success: true, data: result.data };
return { success: false, error: new Error(result.error.message) };
}Testing with Vitest
import { describe, it, expect, vi } from "vitest";
describe("UserService", () => {
it("creates user with valid data", async () => {
const mockRepo = { create: vi.fn().mockResolvedValue({ id: "1", name: "Test" }) };
const service = new UserService(mockRepo);
const user = await service.create({ name: "Test", email: "t@t.com" });
expect(user.id).toBe("1");
expect(mockRepo.create).toHaveBeenCalledOnce();
});
});Anti-Patterns
-
Read more
name: typescript-patterns description: "TypeScript types: generics, discriminated unions, Zod, satisfies, branded types. Triggers: TypeScript, TS, generics, Zod, satisfies, strict mode." effort: medium user-invocable: false allowed-tools: Read
TypeScript/JavaScript Patterns
TypeScript Configuration
Strict tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ES2022",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}ESLint + Prettier
// .eslintrc.json (flat config recommended for new projects)
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended-type-checked",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error"
}
}React Patterns
Functional Component
interface UserCardProps {
user: User;
onSelect?: (id: string) => void;
}
export function UserCard({ user, onSelect }: UserCardProps) {
const handleClick = useCallback(() => {
onSelect?.(user.id);
}, [user.id, onSelect]);
return (
<div onClick={handleClick} role="button" tabIndex={0}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}Custom Hook
function useAsync<T>(fn: () => Promise<T>, deps: unknown[]) {
const [state, setState] = useState<{
data: T | null;
error: Error | null;
loading: boolean;
}>({ data: null, error: null, loading: true });
useEffect(() => {
let cancelled = false;
setState(prev => ({ ...prev, loading: true }));
fn()
.then(data => { if (!cancelled) setState({ data, error: null, loading: false }); })
.catch(error => { if (!cancelled) setState({ data: null, error, loading: false }); });
return () => { cancelled = true; };
}, deps);
return state;
}Context Pattern
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error("useAuth must be within AuthProvider");
return context;
}Next.js App Router
Server Component (default)
// app/users/page.tsx
export default async function UsersPage() {
const users = await db.user.findMany();
return <UserList users={users} />;
}Server Action
"use server";
import { revalidatePath } from "next/cache";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
await db.user.create({ data: { name } });
revalidatePath("/users");
}Node.js/Express Patterns
Type-safe Route Handler
import { Router, Request, Response } from "express";
import { z } from "zod";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
});
router.post("/users", async (req: Request, res: Response) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
const user = await userService.create(result.data);
res.status(201).json(user);
});State Management
Zustand Store
import { create } from "zustand";
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
total: () => number;
}
export const useCartStore = create<CartStore>((set, get) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
removeItem: (id) => set((state) => ({ items: state.items.filter(i => i.id !== id) })),
total: () => get().items.reduce((sum, item) => sum + item.price, 0),
}));Type-safe API (tRPC / Zod)
Zod Schema Validation
import { z } from "zod";
export const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["admin", "user", "guest"]),
createdAt: z.coerce.date(),
});
export type User = z.infer<typeof UserSchema>;Error Handling
class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code: string = "INTERNAL_ERROR"
) {
super(message);
this.name = "AppError";
}
}
// Result pattern (no exceptions)
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function parseUser(raw: unknown): Result<User> {
const result = UserSchema.safeParse(raw);
if (result.success) return { success: true, data: result.data };
return { success: false, error: new Error(result.error.message) };
}Testing with Vitest
import { describe, it, expect, vi } from "vitest";
describe("UserService", () => {
it("creates user with valid data", async () => {
const mockRepo = { create: vi.fn().mockResolvedValue({ id: "1", name: "Test" }) };
const service = new UserService(mockRepo);
const user = await service.create({ name: "Test", email: "t@t.com" });
expect(user.id).toBe("1");
expect(mockRepo.create).toHaveBeenCalledOnce();
});
});Anti-Patterns
-
Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork,
Repo: softspark/ai-toolkit
Other skills on ai-toolkit.
- /ai-toolkit-rules
Mandatory engineering, security, testing, git, performance, quality, and response rules. Claude MUST load this skill for every technical, coding, debugging, review, architecture, DevOps, data, or file-editing task in Chat or Cowork.
Open skill - /mem-search
Search past coding sessions using natural language. Finds relevant observations, decisions, and context from previous work.
Open skill - /a11y-validate
Accessibility validator: WCAG 2.1 AA, EN 301 549, EAA. Triggers: a11y, accessibility, WCAG, EAA, ARIA, contrast, keyboard, screen reader.
Open skill - /agent-creator
Creates new specialized agents with frontmatter, tools, delegation. Triggers: new agent, create agent, agent scaffold, specialized agent.
Open skill - /analyze
Analyzes code quality, complexity, patterns across codebase. Triggers: quality report, hotspot scan, code analysis, architecture signal.
Open skill - /api-patterns
REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit.
Open skill

