| name | TypeScript Ecosystem |
| description | This skill should be used when the user asks to "write typescript", "typescript config", "tsconfig", "type definition", "generics", "utility types", or works with TypeScript language patterns and configuration. Provides comprehensive TypeScript ecosystem patterns and best practices. |
| version | 2.0.0 |
Provide comprehensive patterns for TypeScript language, configuration, type system, and tooling integration.
Read - Analyze tsconfig.json and TypeScript source files
Edit - Modify TypeScript configurations and source code
Bash - Run tsc, tsx, eslint, and build commands
mcp__context7__get-library-docs - Fetch latest TypeScript documentation
Enable all strict checking options (strict: true) for maximum type safety
nodenext for ESM Node.js, bundler for Vite/esbuild/webpack projects
Built-in generic types: Partial, Required, Pick, Omit, Record, Extract, Exclude, ReturnType
Use type guards (typeof, instanceof, in, custom predicates) to safely narrow union types
Node.js version-specific recommended configurations
Active LTS - use ES2024 for stable features
Current release - use ES2025 for latest features
{
"compilerOptions": {
"target": "ES2024",
"lib": ["ES2024"],
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Node.js 24 Active LTS - use ES2024 target/lib for stable features
{
"compilerOptions": {
"target": "ES2025",
"lib": ["ES2025"],
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Node.js 26 current release - use ES2025 target/lib for latest features
<strict_options>
Enables all strict type-checking options
null and undefined handled explicitly
Stricter function type checking
Check bind, call, apply methods
Class properties must be initialized
Error on implicit any types
Error on missing return statements
Error on implicit this
Add undefined to index signatures
Error on unused local variables
Error on unused parameters
</strict_options>
<module_resolution>
Modern Node.js ESM resolution (recommended)
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext"
}
}
Requires "type": "module" in package.json
<pattern name="bundler">
<description>For projects using bundlers (Vite, esbuild, webpack)</description>
<example>
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler"
}
}
</example>
</pattern>
<pattern name="path_aliases">
<description>Import path aliases</description>
<example>
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
}
}
</example>
<warning>baseUrl: deprecated in TS 6.x and expected to be removed in TS 7.x; prefer paths without baseUrl</warning>
<warning>moduleResolution: "node" (alias "node10"): removed in TS 6.0; use "nodenext" or "bundler"</warning>
<decision_tree name="when_to_use">
<question>Are you using a bundler or working with Node.js modules?</question>
<if_yes>Configure appropriate moduleResolution: bundler for bundlers, nodenext for Node.js</if_yes>
<if_no>Stick with default module resolution for simple projects</if_no>
</decision_tree>
</pattern>
</module_resolution>
<project_references>
Monorepo and incremental builds
{
"compilerOptions": {
"composite": true,
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo"
},
"references": [
{ "path": "../shared" },
{ "path": "../core" }
]
}
Use tsc --build for incremental compilation
</project_references>
<native_typescript>
Node.js 24+ runs TypeScript natively without flags or external runners. Node.js 22.6+ required --experimental-strip-types.
Run .ts files directly with Node.js 24+ (no tsx/ts-node needed)
node src/index.ts
Node.js strips type annotations at runtime. Code must be "erasable syntax only" — no enums, namespaces, or parameter properties with runtime semantics.
Enable erasableSyntaxOnly in tsconfig to ensure compatibility with Node.js native execution
{
"compilerOptions": {
"erasableSyntaxOnly": true
}
}
Errors on constructs that Node.js cannot strip (const enums, namespaces with runtime code, legacy parameter properties).
</native_typescript>
<type_patterns>
<utility_types>
Make all properties optional
Make all properties required
Make all properties readonly
Object type with key K and value V
Select specific properties from T
Exclude specific properties from T
Exclude types from union
Extract types from union
Remove null and undefined
Get function return type
Get function parameter types as tuple
Unwrap Promise type
</utility_types>
Basic generic function
function identity<T>(arg: T): T {
return arg;
}
<pattern name="constraints">
<description>Generic with type constraints</description>
<example>
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
</example>
</pattern>
<pattern name="default_type">
<description>Generic with default type parameter</description>
<example>
interface Container<T = string> {
value: T;
}
</example>
</pattern>
<pattern name="multiple_constraints">
<description>Multiple generic parameters with constraints</description>
<example>
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
</example>
</pattern>
<conditional_types>
Basic conditional type
type IsString<T> = T extends string ? true : false;
<pattern name="infer">
<description>Extract types within conditional</description>
<example>
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type ArrayElement<T> = T extends (infer E)[] ? E : never;
</example>
</pattern>
<pattern name="distributive">
<description>Distributes over union types</description>
<example>
type ToArray<T> = T extends any ? T[] : never;
// ToArray<string | number> = string[] | number[]
</example>
</pattern>
</conditional_types>
<mapped_types>
Basic mapped type
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
<pattern name="key_remapping">
<description>Map keys with renaming</description>
<example>
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
</example>
</pattern>
<pattern name="filtering">
<description>Filter properties by type</description>
<example>
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
</example>
</pattern>
</mapped_types>
<template_literal_types>
Template literal type construction
type EventName = on${Capitalize<string>};
type Locale = ${Language}-${Country};
<pattern name="inference">
<description>Extract parameters from template literals</description>
<example>
type ExtractRouteParams<T> = T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
</example>
</pattern>
</template_literal_types>
<type_guards>
Built-in typeof type guard
function process(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
<pattern name="instanceof">
<description>Built-in instanceof type guard</description>
<example>
function handle(error: Error | string) {
if (error instanceof Error) {
return error.message;
}
return error;
}
</example>
</pattern>
<pattern name="custom">
<description>Custom type guard function</description>
<example>
interface Cat { meow(): void; }
interface Dog { bark(): void; }
function isCat(pet: Cat | Dog): pet is Cat {
return (pet as Cat).meow !== undefined;
}
</example>
<decision_tree name="when_to_use">
<question>Does TypeScript need help narrowing union types?</question>
<if_yes>Implement custom type guard with is predicate</if_yes>
<if_no>Use built-in typeof or instanceof guards</if_no>
</decision_tree>
</pattern>
<pattern name="in_operator">
<description>Property existence type guard</description>
<example>
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}
</example>
</pattern>
</type_guards>
<branded_types>
Nominal typing via branding
type UserId = string & { readonly **brand: unique symbol };
type OrderId = string & { readonly **brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
</example>
<note>Prevent mixing similar primitive types</note>
</pattern>
</branded_types>
<satisfies_operator>
Type checking without widening
const config = {
endpoint: "/api",
timeout: 3000,
} satisfies Record<string, string | number>;
// config.endpoint is inferred as "/api" (literal), not string
</satisfies_operator>
</type_patterns>
<runtime_patterns>
<error_handling>
Rust-inspired Result type for error handling
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function parseJson<T>(json: string): Result<T> {
try {
return { success: true, data: JSON.parse(json) };
} catch (e) {
return { success: false, error: e as Error };
}
}
</example>
<decision_tree name="when_to_use">
<question>Do you need explicit error handling without exceptions?</question>
<if_yes>Use Result type for functional error handling</if_yes>
<if_no>Use try-catch for traditional exception handling</if_no>
</decision_tree>
</pattern>
<pattern name="custom_errors">
<description>Custom error classes with additional context</description>
<example>
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
public readonly code: string
) {
super(message);
this.name = "ValidationError";
}
}
</example>
</pattern>
<pattern name="error_cause">
<description>Error chaining with cause (ES2022+)</description>
<example>
try {
await fetchData();
} catch (e) {
throw new Error("Failed to fetch data", { cause: e });
}
</example>
</pattern>
</error_handling>
<resource_management>
Explicit Resource Management with using/await using (TC39 Stage 3, TS 5.2+)
function readFile(path: string) {
using file = openFile(path);
return file.read();
// fileSymbol.dispose called automatically
}
async function withConnection() {
await using conn = await getConnection();
return conn.query("SELECT 1");
// conn[Symbol.asyncDispose]() called automatically
}
</example>
<note>Requires "lib": ["ESNext.Disposable"] or target ES2024+. Use DisposableStack for managing multiple resources.</note>
</pattern>
</resource_management>
<async_patterns>
Parallel promise execution
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts(),
]);
<pattern name="promise_allSettled">
<description>Handle mixed success/failure</description>
<example>
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(2),
fetchUser(3),
]);
const successful = results
.filter((r): r is PromiseFulfilledResult<User> => r.status === "fulfilled")
.map((r) => r.value);
</example>
</pattern>
<pattern name="async_iterator">
<description>Async generator for pagination</description>
<example>
async function* paginate<T>(fetchPage: (page: number) => Promise<T[]>) {
let page = 0;
while (true) {
const items = await fetchPage(page++);
if (items.length === 0) break;
yield* items;
}
}
for await (const item of paginate(fetchUsers)) {
console.log(item);
}
</example>
</pattern>
</async_patterns>
<module_patterns>
ES module export patterns
// Named exports
export const helper = () => {};
export type Config = { /* ... _/ };
// Default export
export default class Service {}
// Re-exports
export { util } from "./util.js";
export type { UtilOptions } from "./util.js";
</example>
</pattern>
<pattern name="barrel_exports">
<description>index.ts for clean imports</description>
<example>
// src/components/index.ts
export { Button } from "./Button.js";
export { Input } from "./Input.js";
export type { ButtonProps, InputProps } from "./types.js";
</example>
<warning>Can impact tree-shaking; use sparingly</warning>
</pattern>
<pattern name="dynamic_import">
<description>Code splitting with dynamic imports</description>
<example>
const module = await import("./heavy-module.js");
module.doSomething();
</example>
</pattern>
</module_patterns>
</runtime_patterns>
ESLint 10 with TypeScript (flat config only; eslintrc fully removed in v10)
// eslint.config.js
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
globalIgnores(["dist/", "node_modules/"]),
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
]);
</example>
<note>ESLint 10 locates eslint.config.* from each linted file's directory, not cwd. defineConfig() and globalIgnores() provide type-safe config composition.</note>
</pattern>
<key_rules>
<rule name="@typescript-eslint/no-explicit-any">Prefer unknown over any</rule>
<rule name="@typescript-eslint/no-unused-vars">Detect unused variables</rule>
<rule name="@typescript-eslint/strict-boolean-expressions">Require explicit boolean conditions</rule>
<rule name="@typescript-eslint/no-floating-promises">Require awaiting promises</rule>
<rule name="@typescript-eslint/prefer-nullish-coalescing">Use ?? over ||</rule>
</key_rules>
Prettier configuration for TypeScript
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}
Biome — unified fast linter and formatter (alternative to ESLint+Prettier)
// biome.json
{
"$schema": "https://biomejs.dev/schemas/2.0/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
}
}
Biome is significantly faster than ESLint+Prettier. Supports JS, TS, JSX, TSX, JSON, CSS. Use when unified tooling and speed are priorities.
Vitest 4.x - fast test runner with stable Browser Mode (requires Vite >= 6.0, Node.js >= 20)
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
},
},
});
</example>
<note>Vitest 4 removed the experimental tag from Browser Mode. Visual regression testing available via @vitest/browser-playwright.</note>
</pattern>
<pattern name="workspace">
<description>Vitest workspace for monorepo testing</description>
<example>
// vitest.workspace.ts
import { defineWorkspace } from "vitest/config";
export default defineWorkspace([
"packages/*/vitest.config.ts",
]);
</example>
</pattern>
</vitest>
<jest>
<pattern name="config">
<description>Jest configuration for TypeScript</description>
<example>
// jest.config.ts
import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src"],
moduleNameMapper: {
"^@/(.\*)$": "<rootDir>/src/$1",
},
};
export default config;
</example>
</pattern>
</jest>
<build_tools>
TypeScript compiler commands
<use_case name="compile">tsc - Compile TypeScript</use_case>
<use_case name="build">tsc --build - Incremental build (monorepo)</use_case>
<use_case name="check">tsc --noEmit - Type check only</use_case>
<use_case name="watch">tsc --watch - Watch mode</use_case>
<tsx>
<tool name="tsx">
<description>TypeScript execution with esbuild (fallback for Node.js < 24 or code using non-erasable syntax)</description>
<use_case name="run">tsx src/index.ts - Run TypeScript directly</use_case>
<use_case name="watch">tsx watch src/index.ts - Watch mode</use_case>
</tool>
</tsx>
<tsup>
<pattern name="config">
<description>Bundle TypeScript libraries</description>
<example>
// tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
clean: true,
sourcemap: true,
});
</example>
</pattern>
</tsup>
</build_tools>
<typescript_versions>
<current_stable>
TypeScript 6.0
Final JavaScript-based compiler release. Bridge release between TS 5.x and 7.0. No 6.1 planned.
npm install -D typescript
</current_stable>
TypeScript 7.0
Go-native compiler rewrite with up to 10x performance improvement. New language service with ~8x project load time reduction.
npm install -D @typescript/native-preview
Near completion; nightly preview available. TS 7.0 is the future of TypeScript.
</typescript_versions>
<context7_integration>
<library_id>/microsoft/typescript</library_id>
<trust_score>9.9</trust_score>
16397
<usage_pattern>
Resolve library ID if needed (already known: /microsoft/typescript)
Workflow guidance
Step completed
Fetch documentation with specific topic
Workflow guidance
Step completed
Configuration options and patterns
Generic type patterns
Built-in utility types
Module resolution strategies
Go-native compiler migration
</usage_pattern>
<common_queries>
Strict compiler options
Path aliases configuration
Type declaration generation
ESM support in Node.js
</common_queries>
</context7_integration>
<best_practices>
Enable strict mode in all projects
Use noUncheckedIndexedAccess for safer array/object access
Prefer 'unknown' over 'any' for unknown types
Use 'satisfies' to check types without widening
Create branded types for domain primitives
Use TS 6.x as the current baseline in this repository and prepare for TS 7.0 migration
Use Result types for error handling over exceptions
Keep type definitions close to usage
Export types separately with 'export type'
Use 'const' assertions for literal types
Prefer interfaces for public APIs, types for unions/utilities
</best_practices>
<anti_patterns>
Overusing 'any' defeats type safety
Use 'unknown' and narrow with type guards
Excessive 'as' casts bypass type checking
Use type guards or proper typing
Missing type annotations with noImplicitAny disabled
Enable strict mode, add explicit types
Barrel files can hurt tree-shaking
Use direct imports for large modules
Enums have runtime overhead and quirks
Use const objects with 'as const'
const Status = {
Active: "active",
Inactive: "inactive",
} as const;
type Status = (typeof Status)[keyof typeof Status];
</example>
Namespaces are legacy, use ES modules
Use regular imports/exports
Decorators add complexity; use judiciously even though TC39 decorators are now stable (since TS 5.0)
Prefer composition and higher-order functions for simpler cases
eslintrc configuration format is fully removed in ESLint 10
Use flat config with eslint.config.js and defineConfig()
Enable strict mode in all TypeScript projects
Never use any without documented justification; prefer unknown
Run tsc --noEmit before committing to catch type errors
Use noUncheckedIndexedAccess for safer array/object access
Export types separately with 'export type' for clarity
Define types before implementation for better design
Use satisfies operator to check types without widening
Understand TypeScript code requirements
1. Check tsconfig.json for project settings
Workflow guidance
Step completed
2. Review existing type patterns in project
Workflow guidance
Step completed
3. Identify type dependencies and imports
Workflow guidance
Step completed
Write type-safe TypeScript code
1. Define types before implementation
Workflow guidance
Step completed
2. Use strict type checking features
Workflow guidance
Step completed
3. Follow project naming conventions
Workflow guidance
Step completed
Verify TypeScript correctness
1. Run tsc --noEmit for type checking
Workflow guidance
Step completed
2. Check with ESLint for style issues
Workflow guidance
Step completed
3. Verify tests pass
Workflow guidance
Step completed
<error_escalation>
Minor type inference issue
Add explicit type annotation
Type error in implementation
Fix type, verify with tsc
Breaking type change in public API
Stop, present migration options to user
Type safety bypass with any or type assertion
Block operation, require proper typing
</error_escalation>
Enable strict mode in tsconfig.json
Define explicit types for public APIs
Use type guards for runtime type checking
Using any type without justification
Type assertions without validation
Ignoring TypeScript errors with ts-ignore
<related_skills>
Symbol-level navigation for type definitions and interfaces
Fetch latest TypeScript compiler and tooling documentation
Debug type errors and investigate compilation issues
</related_skills>