plugins/typescript-development/skills/mastering-typescript/SKILL.md
Enterprise-grade TypeScript depth covering the advanced type system (generics, mapped/conditional types, satisfies operator, branded types, discriminated unions, template literal types), JS-to-TS migration playbooks, modern toolchain setup (tsconfig strict, Vite 7, Vitest, ESLint 9 flat config, pnpm), validation patterns with Zod, type-safe React integration, NestJS API design, and LangChain.js typing. TRIGGER WHEN: migrating a JavaScript codebase to TypeScript, bootstrapping a new TS project (tsconfig + ESLint + Vite/Vitest), writing or refactoring code that uses advanced type patterns (generics, mapped/conditional types, satisfies, branded types), designing Zod schemas for runtime validation, building type-safe NestJS APIs, doing deep React + TypeScript work (typed hooks, generic components, discriminated props), or typing LangChain.js applications. Also trigger when comparing TypeScript with Java/Python enterprise approaches. DO NOT TRIGGER WHEN: writing routine TS/JS without advanced type work (use typescript-development:typescript-write), reviewing React performance specifically (use react-development:review-react), tuning Node.js bundling/packaging unrelated to TS, or doing dead-code detection alone (use typescript-development:knip).
npx skillsauth add acaprino/anvil-toolset mastering-typescriptInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Source: SpillwaveSolutions/mastering-typescript-skill - mastering-typescript/SKILL.md
Build enterprise-grade, type-safe applications with TypeScript 5.9+.
Compatibility: TypeScript 5.9+, Node.js 22 LTS, Vite 7, NestJS 11, React 19
# Initialize TypeScript project with ESM
pnpm create vite@latest my-app --template vanilla-ts
cd my-app && pnpm install
# Configure strict TypeScript
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2024",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
EOF
Use when:
Before starting any TypeScript project:
- [ ] Use pnpm for package management (faster, disk-efficient)
- [ ] Configure ESM-first (type: "module" in package.json)
- [ ] Enable strict mode in tsconfig.json
- [ ] Set up ESLint with @typescript-eslint
- [ ] Add Prettier for consistent formatting
- [ ] Configure Vitest for testing
const name: string = "Alice";
const age: number = 30;
const active: boolean = true;
const id: bigint = 9007199254740991n;
const key: symbol = Symbol("unique");
// Union: value can be one of several types
type Status = "pending" | "approved" | "rejected";
// Intersection: value must satisfy all types
type Employee = Person & { employeeId: string };
// Discriminated union for type-safe handling
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult<T>(result: Result<T>): T | null {
if (result.success) {
return result.data; // TypeScript knows data exists here
}
console.error(result.error);
return null;
}
// typeof guard
function process(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
// Custom type guard
interface User { type: "user"; name: string }
interface Admin { type: "admin"; permissions: string[] }
function isAdmin(person: User | Admin): person is Admin {
return person.type === "admin";
}
satisfies Operator (TS 5.0+)Validate type conformance while preserving inference:
// Problem: Type assertion loses specific type info
const colors1 = {
red: "#ff0000",
green: "#00ff00"
} as Record<string, string>;
colors1.red.toUpperCase(); // OK, but red could be undefined
// Solution: satisfies preserves literal types
const colors2 = {
red: "#ff0000",
green: "#00ff00"
} satisfies Record<string, string>;
colors2.red.toUpperCase(); // OK, and TypeScript knows red exists
function first<T>(items: T[]): T | undefined {
return items[0];
}
const num = first([1, 2, 3]); // number | undefined
const str = first(["a", "b"]); // string | undefined
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello"); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength(42); // Error: number has no length
interface ApiResponse<T> {
data: T;
status: number;
timestamp: Date;
}
async function fetchUser(id: string): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return {
data,
status: response.status,
timestamp: new Date()
};
}
| Type | Purpose | Example |
|------|---------|---------|
| Partial<T> | All properties optional | Partial<User> |
| Required<T> | All properties required | Required<Config> |
| Pick<T, K> | Select specific properties | Pick<User, "id" \| "name"> |
| Omit<T, K> | Exclude specific properties | Omit<User, "password"> |
| Record<K, V> | Object with typed keys/values | Record<string, number> |
| ReturnType<F> | Extract function return type | ReturnType<typeof fn> |
| Parameters<F> | Extract function parameters | Parameters<typeof fn> |
| Awaited<T> | Unwrap Promise type | Awaited<Promise<User>> |
// Basic conditional type
type IsString<T> = T extends string ? true : false;
// Extract array element type
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Numbers = ArrayElement<number[]>; // number
type Strings = ArrayElement<string[]>; // string
// Practical: Extract Promise result type
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
// Make all properties readonly
type Immutable<T> = {
readonly [K in keyof T]: T[K];
};
// Make all properties nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Create getter functions for each property
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person { name: string; age: number }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
// Typed functional component
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
}
const Button: React.FC<ButtonProps> = ({ label, onClick, variant = "primary" }) => (
<button className={variant} onClick={onClick}>
{label}
</button>
);
// Typed hooks
const [count, setCount] = useState<number>(0);
const userRef = useRef<HTMLInputElement>(null);
// Type-safe DTO with class-validator
import { IsString, IsEmail, MinLength } from 'class-validator';
class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
}
// Or with Zod (modern approach)
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email()
});
type CreateUserDto = z.infer<typeof CreateUserSchema>;
See react-integration.md and nestjs-integration.md for detailed patterns.
import { z } from 'zod';
// Define schema
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["user", "admin", "moderator"]),
createdAt: z.coerce.date()
});
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;
// Validate at runtime
function parseUser(data: unknown): User {
return UserSchema.parse(data); // Throws ZodError if invalid
}
// Safe parsing (returns result object)
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // Typed as User
} else {
console.error(result.error.issues);
}
| Tool | Version | Purpose | |------|---------|---------| | TypeScript | 5.9+ | Type checking and compilation | | Node.js | 22 LTS | Runtime environment | | Vite | 7.x | Build tool and dev server | | pnpm | 9.x | Package manager | | ESLint | 9.x | Linting with flat config | | Vitest | 3.x | Testing framework | | Prettier | 3.x | Code formatting |
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
}
);
allowJs: true and checkJs: false to tsconfig.json.js to .ts one at a time// Before full migration, use JSDoc
/**
* @param {string} name
* @param {number} age
* @returns {User}
*/
function createUser(name, age) {
return { name, age };
}
See enterprise-patterns.md for comprehensive migration guides.
| Mistake | Problem | Fix |
|---------|---------|-----|
| Using any liberally | Defeats type safety | Use unknown and narrow |
| Ignoring strict mode | Misses null/undefined bugs | Enable all strict options |
| Type assertions (as) | Can hide type errors | Use satisfies or guards |
| Enum for simple unions | Generates runtime code | Use literal unions instead |
| Not validating API data | Runtime type mismatches | Use Zod at boundaries |
| Feature | TypeScript | Java | Python |
|---------|------------|------|--------|
| Type System | Structural | Nominal | Gradual (duck typing) |
| Nullability | Explicit (T \| null) | @Nullable annotations | Optional via typing |
| Generics | Type-level, erased | Type-level, erased | Runtime via typing |
| Interfaces | Structural matching | Must implement | Protocol (3.8+) |
| Enums | Avoid (use unions) | First-class | Enum class |
development
Quality gates for multi-reviewer code review pipelines: adversarial verification panel, completeness critic, reviewer pipeline conventions, and the context sharing pattern for parallel reviewers. TRIGGER WHEN: running /senior-review:team-review quality gates; running /senior-review:code-review Steps 4b/4c (adversarial verification and completeness check); consolidating or deduplicating findings from multiple parallel reviewers. DO NOT TRIGGER WHEN: single-reviewer style review without a consolidation phase, or generic team coordination (the upstream agent-teams skills cover that).
development
Knowledge base for pure-architecture decisions on when to unify duplicated logic into a shared abstraction versus leave it duplicated. Covers the canonical theory (Rule of Three, DRY/WET/AHA, Wrong Abstraction, Locality of Behaviour, Bounded Contexts, Tidy First options framing, CUPID vs SOLID), 12 essential-duplication patterns that justify unification, 12 wrong-abstraction patterns that justify inlining or decomposition, an operational decision frame, and a verified reading list. TRIGGER WHEN: the user is making an architectural decision about whether to centralize, extract, or remove a layer; reviewing an abstraction for premature generality; auditing scattered cross-cutting concerns; spawned by the abstraction-architect agent during /abstraction-architect:audit or as the Abstraction dimension of /senior-review:team-review or /senior-review:code-review; the user asks "should I extract this into a service" / "is this DRY enough" / "is this wrong abstraction". DO NOT TRIGGER WHEN: the task is code formatting and readability cleanup (use clean-code:clean-code), Python-specific refactoring with metrics (use python-development:python-refactor), generic dead-code removal (use senior-review:cleanup-dead-code), security review (use senior-review:security-auditor), or pure pattern-consistency review without an architecture lens (use senior-review:code-auditor).
development
Unified web frontend knowledge base covering CSS architecture, UX psychology, UI components, distinctive aesthetics, and interface design generation. TRIGGER WHEN: working on web styling, design systems, component decisions, responsive strategy, distinctive frontend aesthetics, or exploring multiple interface designs. DO NOT TRIGGER WHEN: the task is purely backend or unrelated to web frontend.
development
Stripe payments knowledge base - API patterns, checkout optimization, subscription lifecycle, pricing strategies, webhook reliability, Firebase integration, cost analysis, and revenue modeling. Loaded by stripe-integrator and revenue-optimizer agents; also consumable directly when the user asks for Stripe-specific patterns without needing an agent. TRIGGER WHEN: working with Stripe API (Payment Intents, Customers, Subscriptions, Checkout Sessions, Connect, webhooks, tax, usage-based billing), pricing strategy, or revenue modeling. DO NOT TRIGGER WHEN: payment work is non-Stripe (PayPal, Square, crypto) or the task is generic e-commerce unrelated to payments.