
Typescript Pro
- 6.7k installs
- 10.8k repo stars
- Updated May 20, 2026
- jeffallan/claude-skills
How do I design type-safe APIs using advanced TypeScript features like generics, branded types, and tRPC?
About
TypeScript Pro implements advanced type systems including generics, conditional types, mapped types, discriminated unions, and branded types for domain modeling. Developers use it when building applications requiring strict type safety across full-stack boundaries, monorepo setups, or tRPC-based APIs. Core workflows cover type architecture analysis, type-first API design, type guard implementation, build optimization via project references and incremental compilation, and validation with type-coverage tools. Enforces strict compiler flags, declaration file generation, and exhaustive type checking while forbidding unsafe casts and enum use. --- name: typescript-pro description: Implements advanced TypeScript type systems, creates custom type guards, utility types, and branded types, and configures tRPC for end-to-end type safety. Use when building TypeScript applications requiring advanced generics, conditional or mapped types, discriminated unions, monorepo setup, or full-stack type safety with tRPC. license: MIT metadata: author: https://github.com/Jeffallan version: "1.1.0" domain: language triggers: TypeScript, generics, type safety, conditional types, mapped types, tRPC, tscon.
- Branded types for domain modeling prevent accidental id mix-ups at compile time
- Discriminated unions with type predicates enable exhaustive state machine patterns
- Custom utility types (DeepReadonly, RequireExactlyOne) for advanced type composition
- tsconfig strict mode with noUncheckedIndexedAccess, noImplicitOverride, exactOptionalPropertyTypes
- Type-first API design with tRPC for end-to-end type safety across full-stack boundaries
Typescript Pro by the numbers
- 6,673 all-time installs (skills.sh)
- +891 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #124 of 4,386 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
typescript-pro capabilities & compatibility
- Capabilities
- advanced generics and conditional types · branded type creation for domain modeling · discriminated union pattern implementation · custom utility type composition · trpc full stack type safety setup · tsconfig optimization for type checking
- Use cases
- api development
- Platforms
- macOS · Windows · Linux
- Runs
- Runs locally
- Pricing
- Free
What typescript-pro says it does
Implements advanced TypeScript type systems, creates custom type guards, utility types, and branded types, and configures tRPC for end-to-end type safety.
npx skills add https://github.com/jeffallan/claude-skills --skill typescript-proAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6.7k |
|---|---|
| repo stars | ★ 10.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | May 20, 2026 |
| Repository | jeffallan/claude-skills ↗ |
What it does
Build type-safe full-stack applications using advanced TypeScript generics, branded types, and tRPC for compile-time API validation.
Who is it for?
Full-stack TypeScript applications, monorepo setups, tRPC APIs, state machines, domain-driven design requiring strict type safety.
Skip if: Quick prototypes, codebases unwilling to enable strict mode, projects using untyped JavaScript libraries.
When should I use this skill?
Building TypeScript applications with generics, conditional types, discriminated unions, or implementing tRPC for end-to-end type safety.
What you get
Type-safe full-stack applications with zero type errors, exhaustive state handling, and compile-time API validation.
- Type definitions (interfaces, branded types, generics)
- Implementation with type guards
- tsconfig.json configuration
By the numbers
- Recommended tsconfig with 12+ strict compiler flags
- 5 core workflow steps from analysis to type validation
- 4 reference guides: advanced types, type guards, utility types, configuration
Files
TypeScript Pro
Core Workflow
1. Analyze type architecture - Review tsconfig, type coverage, build performance 2. Design type-first APIs - Create branded types, generics, utility types 3. Implement with type safety - Write type guards, discriminated unions, conditional types; run tsc --noEmit to catch type errors before proceeding 4. Optimize build - Configure project references, incremental compilation, tree shaking; re-run tsc --noEmit to confirm zero errors after changes 5. Test types - Confirm type coverage with a tool like type-coverage; validate that all public APIs have explicit return types; iterate on steps 3–4 until all checks pass
Reference Guide
Load detailed guidance based on context:
| Topic | Reference | Load When |
|---|---|---|
| Advanced Types | references/advanced-types.md | Generics, conditional types, mapped types, template literals |
| Type Guards | references/type-guards.md | Type narrowing, discriminated unions, assertion functions |
| Utility Types | references/utility-types.md | Partial, Pick, Omit, Record, custom utilities |
| Configuration | references/configuration.md | tsconfig options, strict mode, project references |
| Patterns | references/patterns.md | Builder pattern, factory pattern, type-safe APIs |
Code Examples
Branded Types
// Branded type for domain modeling
type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<number, "OrderId">;
const toUserId = (id: string): UserId => id as UserId;
const toOrderId = (id: number): OrderId => id as OrderId;
// Usage — prevents accidental id mix-ups at compile time
function getOrder(userId: UserId, orderId: OrderId) { /* ... */ }Discriminated Unions & Type Guards
type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: string[] };
type ErrorState = { status: "error"; error: Error };
type RequestState = LoadingState | SuccessState | ErrorState;
// Type predicate guard
function isSuccess(state: RequestState): state is SuccessState {
return state.status === "success";
}
// Exhaustive switch with discriminated union
function renderState(state: RequestState): string {
switch (state.status) {
case "loading": return "Loading…";
case "success": return state.data.join(", ");
case "error": return state.error.message;
default: {
const _exhaustive: never = state;
throw new Error(`Unhandled state: ${_exhaustive}`);
}
}
}Custom Utility Types
// Deep readonly — immutable nested objects
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// Require exactly one of a set of keys
type RequireExactlyOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>> &
{ [K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, never>> }[Keys];Recommended tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"incremental": true,
"skipLibCheck": false
}
}Constraints
MUST DO
- Enable strict mode with all compiler flags
- Use type-first API design
- Implement branded types for domain modeling
- Use
satisfiesoperator for type validation - Create discriminated unions for state machines
- Use
Annotatedpattern with type predicates - Generate declaration files for libraries
- Optimize for type inference
MUST NOT DO
- Use explicit
anywithout justification - Skip type coverage for public APIs
- Mix type-only and value imports
- Disable strict null checks
- Use
asassertions without necessity - Ignore compiler performance warnings
- Skip declaration file generation
- Use enums (prefer const objects with
as const)
Output Templates
When implementing TypeScript features, provide: 1. Type definitions (interfaces, types, generics) 2. Implementation with type guards 3. tsconfig configuration if needed 4. Brief explanation of type design decisions
Knowledge Reference
TypeScript 5.0+, generics, conditional types, mapped types, template literal types, discriminated unions, type guards, branded types, tRPC, project references, incremental compilation, declaration files, const assertions, satisfies operator
Advanced Types
Generic Constraints
// Basic constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Multiple constraints
interface HasId { id: number; }
interface HasName { name: string; }
function merge<T extends HasId, U extends HasName>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
// Generic constraint with default
type ApiResponse<T = unknown, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
// Constraint with infer
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // stringConditional Types
// Basic conditional type
type IsString<T> = T extends string ? true : false;
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumberArray = ToArray<string | number>; // string[] | number[]
// Non-distributive (use tuple)
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type BothArray = ToArrayNonDist<string | number>; // (string | number)[]
// Nested conditionals for type extraction
type Flatten<T> = T extends Array<infer U>
? U extends Array<infer V>
? Flatten<V>
: U
: T;
type Nested = Flatten<string[][][]>; // string
// Exclude null/undefined
type NonNullable<T> = T extends null | undefined ? never : T;Mapped Types
// Basic mapped type
type ReadOnly<T> = {
readonly [K in keyof T]: T[K];
};
// Optional properties
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Required properties
type Required<T> = {
[K in keyof T]-?: T[K]; // Remove optional modifier
};
// Key remapping with 'as'
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; }
// Filtering keys
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
type StringFields = PickByType<Person, string>; // { name: string }Template Literal Types
// Basic template literal
type EmailLocale = 'en' | 'es' | 'fr';
type EmailType = 'welcome' | 'reset-password';
type EmailTemplate = `${EmailLocale}_${EmailType}`;
// 'en_welcome' | 'en_reset-password' | 'es_welcome' | ...
// Intrinsic string manipulation
type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
// Template literal with mapped types
type CSSProperties = {
[K in 'color' | 'background' | 'border' as `--${K}`]: string;
};
// { '--color': string; '--background': string; '--border': string }
// Pattern matching with infer
type ExtractRouteParams<T extends string> =
T extends `${infer _Start}/:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<`/${Rest}`>
: T extends `${infer _Start}/:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<'/users/:id/posts/:postId'>; // 'id' | 'postId'Higher-Kinded Types (Simulation)
// Type-level function simulation
interface TypeClass<F> {
map: <A, B>(f: (a: A) => B, fa: any) => any;
}
// Functor pattern
type Maybe<T> = { type: 'just'; value: T } | { type: 'nothing' };
const MaybeFunctor: TypeClass<Maybe<any>> = {
map: <A, B>(f: (a: A) => B, ma: Maybe<A>): Maybe<B> => {
return ma.type === 'just'
? { type: 'just', value: f(ma.value) }
: { type: 'nothing' };
}
};
// Builder pattern with generics
type Builder<T, K extends keyof T = never> = {
with<P extends Exclude<keyof T, K>>(
key: P,
value: T[P]
): Builder<T, K | P>;
build(): K extends keyof T ? T : never;
};Recursive Types
// JSON type
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// Deep partial
type DeepPartial<T> = T extends object ? {
[K in keyof T]?: DeepPartial<T[K]>;
} : T;
// Deep readonly
type DeepReadonly<T> = T extends object ? {
readonly [K in keyof T]: DeepReadonly<T[K]>;
} : T;
// Path type for nested objects
type PathsToProps<T> = T extends object ? {
[K in keyof T]: K extends string
? T[K] extends object
? K | `${K}.${PathsToProps<T[K]>}`
: K
: never;
}[keyof T] : never;
interface User {
profile: {
name: string;
settings: {
theme: string;
};
};
}
type UserPaths = PathsToProps<User>;
// 'profile' | 'profile.name' | 'profile.settings' | 'profile.settings.theme'Variance and Contravariance
// Covariance (return types)
type Producer<T> = () => T;
let stringProducer: Producer<string> = () => 'hello';
let objectProducer: Producer<object> = stringProducer; // OK: string is object
// Contravariance (parameter types)
type Consumer<T> = (value: T) => void;
let objectConsumer: Consumer<object> = (obj) => console.log(obj);
let stringConsumer: Consumer<string> = objectConsumer; // OK in strict mode
// Invariance (mutable properties)
interface Box<T> {
value: T;
setValue(v: T): void;
}
let stringBox: Box<string> = { value: '', setValue: (v) => {} };
// let objectBox: Box<object> = stringBox; // Error: invariantType-Level Programming
// Type-level addition (limited)
type Length<T extends any[]> = T['length'];
type Concat<A extends any[], B extends any[]> = [...A, ...B];
// Type-level conditionals
type If<Condition extends boolean, Then, Else> =
Condition extends true ? Then : Else;
// Type-level equality
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
// Assert equal types (for testing)
type Assert<T extends true> = T;
type Test = Assert<Equal<1 | 2, 2 | 1>>; // OKQuick Reference
| Pattern | Use Case |
|---|---|
T extends U ? X : Y | Conditional type logic |
infer R | Extract types from patterns |
K in keyof T | Iterate over object keys |
as NewKey | Remap keys in mapped types |
| Template literals | String pattern types |
T extends any | Distributive conditionals |
[T] extends [any] | Non-distributive check |
-? modifier | Remove optional |
readonly modifier | Make immutable |
TypeScript Configuration
Strict Mode Configuration
{
"compilerOptions": {
// Strict type checking
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
// Additional checks
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
// Module resolution
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
// Emit
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": false,
"importHelpers": true,
// Interop
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
// Target
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// Skip checking
"skipLibCheck": true
}
}Project References
// Root tsconfig.json
{
"files": [],
"references": [
{ "path": "./packages/shared" },
{ "path": "./packages/frontend" },
{ "path": "./packages/backend" }
]
}
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"]
}
// packages/frontend/tsconfig.json
{
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../shared" }
],
"include": ["src/**/*"]
}Module Resolution Strategies
// Node16/NodeNext (recommended for Node.js)
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true
}
}
// Bundler (for bundlers like Vite, esbuild)
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"moduleDetection": "force"
}
}
// Classic (legacy, avoid)
{
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "node"
}
}Path Mapping
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@shared/*": ["../shared/src/*"],
"@types": ["src/types/index.ts"]
}
}
}// Usage with path mapping
import { Button } from '@components/Button';
import { formatDate } from '@utils/date';
import type { User } from '@types';Incremental Compilation
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./dist/.tsbuildinfo",
"composite": true
}
}Declaration Files
{
"compilerOptions": {
// Generate .d.ts files
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": false,
// Bundle declarations
"declarationDir": "./types",
// For libraries
"stripInternal": true
}
}// Using JSDoc for .d.ts generation
/**
* Creates a user
* @param name - User's name
* @param email - User's email
* @returns The created user
* @example
* ```ts
* const user = createUser('John', 'john@example.com');
* ```
*/
export function createUser(name: string, email: string): User {
return { id: generateId(), name, email };
}Build Optimization
{
"compilerOptions": {
// Performance
"skipLibCheck": true,
"skipDefaultLibCheck": true,
// Faster builds
"incremental": true,
"assumeChangesOnlyAffectDirectDependencies": true,
// Smaller output
"removeComments": true,
"importHelpers": true,
// Tree shaking support
"module": "ESNext",
"target": "ES2020"
}
}Multiple Configurations
// tsconfig.json (base)
{
"compilerOptions": {
"strict": true,
"target": "ES2022"
}
}
// tsconfig.build.json (production)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"removeComments": true,
"declaration": true
},
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
}
// tsconfig.test.json (testing)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "node"],
"esModuleInterop": true
},
"include": ["src/**/*.test.ts", "src/**/*.spec.ts"]
}Framework-Specific Configs
// React + Vite
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true
}
}
// Next.js
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./src/*"] }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
// Node.js + Express
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true
}
}Custom Type Definitions
// src/types/global.d.ts
declare global {
interface Window {
myApp: {
version: string;
config: AppConfig;
};
}
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
API_KEY: string;
NODE_ENV: 'development' | 'production' | 'test';
}
}
}
export {};
// src/types/modules.d.ts
declare module '*.svg' {
const content: string;
export default content;
}
declare module '*.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module 'untyped-library' {
export function doSomething(value: string): number;
}Compiler API Usage
// programmatic compilation
import ts from 'typescript';
function compile(fileNames: string[], options: ts.CompilerOptions): void {
const program = ts.createProgram(fileNames, options);
const emitResult = program.emit();
const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
allDiagnostics.forEach(diagnostic => {
if (diagnostic.file) {
const { line, character } = ts.getLineAndCharacterOfPosition(
diagnostic.file,
diagnostic.start!
);
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n'
);
console.log(
`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`
);
} else {
console.log(
ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')
);
}
});
const exitCode = emitResult.emitSkipped ? 1 : 0;
console.log(`Process exiting with code '${exitCode}'.`);
process.exit(exitCode);
}
compile(['src/index.ts'], {
noEmitOnError: true,
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ES2022,
strict: true
});Performance Monitoring
{
"compilerOptions": {
"diagnostics": true,
"extendedDiagnostics": true,
"generateCpuProfile": "profile.cpuprofile",
"explainFiles": true
}
}# Run with diagnostics
tsc --diagnostics
# Extended diagnostics
tsc --extendedDiagnostics
# Generate trace
tsc --generateTrace trace
# Analyze with @typescript/analyze-trace
npx @typescript/analyze-trace traceQuick Reference
| Option | Purpose |
|---|---|
strict | Enable all strict checks |
composite | Enable project references |
incremental | Enable incremental compilation |
skipLibCheck | Skip .d.ts checking for faster builds |
esModuleInterop | Better CommonJS interop |
moduleResolution | How modules are resolved |
paths | Path mapping for imports |
declaration | Generate .d.ts files |
sourceMap | Generate source maps |
noEmit | Don't emit output (type check only) |
isolatedModules | Each file can be transpiled separately |
allowImportingTsExtensions | Import .ts files directly |
TypeScript Patterns
Builder Pattern
// Type-safe builder with progressive types
class UserBuilder {
private data: Partial<User> = {};
setName(name: string): this {
this.data.name = name;
return this;
}
setEmail(email: string): this {
this.data.email = email;
return this;
}
setAge(age: number): this {
this.data.age = age;
return this;
}
build(): User {
if (!this.data.name || !this.data.email) {
throw new Error('Name and email are required');
}
return this.data as User;
}
}
// Fluent API with type safety
const user = new UserBuilder()
.setName('John')
.setEmail('john@example.com')
.setAge(30)
.build();
// Advanced builder with compile-time validation
type Builder<T, K extends keyof T = never> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (
value: T[P]
) => Builder<T, K | P>;
} & {
build: K extends keyof T ? () => T : never;
};
function createBuilder<T>(): Builder<T> {
const data = {} as T;
return new Proxy({} as Builder<T>, {
get(_, prop: string) {
if (prop === 'build') {
return () => data;
}
if (prop.startsWith('set')) {
const key = prop.slice(3).toLowerCase();
return (value: any) => {
(data as any)[key] = value;
return this;
};
}
}
});
}Factory Pattern
// Abstract factory with type safety
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message);
}
}
class FileLogger implements Logger {
constructor(private filename: string) {}
log(message: string): void {
// Write to file
}
}
type LoggerType = 'console' | 'file';
type LoggerConfig<T extends LoggerType> = T extends 'file'
? { type: T; filename: string }
: { type: T };
class LoggerFactory {
static create<T extends LoggerType>(config: LoggerConfig<T>): Logger {
switch (config.type) {
case 'console':
return new ConsoleLogger();
case 'file':
return new FileLogger(config.filename);
default:
throw new Error('Unknown logger type');
}
}
}
const consoleLogger = LoggerFactory.create({ type: 'console' });
const fileLogger = LoggerFactory.create({ type: 'file', filename: 'app.log' });
// Generic factory with dependency injection
type Constructor<T> = new (...args: any[]) => T;
class Container {
private instances = new Map<Constructor<any>, any>();
register<T>(token: Constructor<T>, instance: T): void {
this.instances.set(token, instance);
}
resolve<T>(token: Constructor<T>): T {
const instance = this.instances.get(token);
if (!instance) {
throw new Error(`No instance registered for ${token.name}`);
}
return instance;
}
}Repository Pattern
// Type-safe repository with generic CRUD
interface Entity {
id: string | number;
}
interface Repository<T extends Entity> {
find(id: T['id']): Promise<T | null>;
findAll(): Promise<T[]>;
create(data: Omit<T, 'id'>): Promise<T>;
update(id: T['id'], data: Partial<Omit<T, 'id'>>): Promise<T>;
delete(id: T['id']): Promise<void>;
}
class UserRepository implements Repository<User> {
async find(id: User['id']): Promise<User | null> {
// Database query
return null;
}
async findAll(): Promise<User[]> {
return [];
}
async create(data: Omit<User, 'id'>): Promise<User> {
// Insert into database
return { id: 1, ...data };
}
async update(id: User['id'], data: Partial<Omit<User, 'id'>>): Promise<User> {
// Update database
return { id, name: '', email: '', ...data };
}
async delete(id: User['id']): Promise<void> {
// Delete from database
}
}
// Query builder with type safety
class QueryBuilder<T> {
private conditions: Array<(item: T) => boolean> = [];
where<K extends keyof T>(key: K, value: T[K]): this {
this.conditions.push(item => item[key] === value);
return this;
}
execute(items: T[]): T[] {
return items.filter(item =>
this.conditions.every(condition => condition(item))
);
}
}
const query = new QueryBuilder<User>()
.where('email', 'john@example.com')
.where('age', 30);Type-Safe API Client
// REST API client with type safety
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
type ApiEndpoints = {
'/users': {
GET: { response: User[] };
POST: { body: CreateUserDto; response: User };
};
'/users/:id': {
GET: { params: { id: string }; response: User };
PUT: { params: { id: string }; body: UpdateUserDto; response: User };
DELETE: { params: { id: string }; response: void };
};
'/posts': {
GET: { query: { userId?: string }; response: Post[] };
POST: { body: CreatePostDto; response: Post };
};
};
type ExtractParams<T extends string> =
T extends `${infer _Start}/:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ExtractParams<`/${Rest}`>
: T extends `${infer _Start}/:${infer Param}`
? { [K in Param]: string }
: {};
class ApiClient {
async request<
Path extends keyof ApiEndpoints,
Method extends keyof ApiEndpoints[Path]
>(
method: Method,
path: Path,
options?: ApiEndpoints[Path][Method] extends { body: infer B }
? { body: B }
: ApiEndpoints[Path][Method] extends { params: infer P }
? { params: P }
: ApiEndpoints[Path][Method] extends { query: infer Q }
? { query: Q }
: never
): Promise<
ApiEndpoints[Path][Method] extends { response: infer R } ? R : never
> {
// Make HTTP request
return null as any;
}
}
const client = new ApiClient();
// Type-safe API calls
const users = await client.request('GET', '/users');
const user = await client.request('GET', '/users/:id', { params: { id: '1' } });
const newUser = await client.request('POST', '/users', {
body: { name: 'John', email: 'john@example.com' }
});State Machine Pattern
// Type-safe state machine
type State = 'idle' | 'loading' | 'success' | 'error';
type Event =
| { type: 'FETCH' }
| { type: 'SUCCESS'; data: any }
| { type: 'ERROR'; error: Error }
| { type: 'RETRY' };
type StateMachine = {
[S in State]: {
[E in Event['type']]?: State;
};
};
const machine: StateMachine = {
idle: { FETCH: 'loading' },
loading: { SUCCESS: 'success', ERROR: 'error' },
success: { FETCH: 'loading' },
error: { RETRY: 'loading' }
};
class StateManager<S extends string, E extends { type: string }> {
constructor(
private state: S,
private transitions: Record<S, Partial<Record<E['type'], S>>>
) {}
getState(): S {
return this.state;
}
dispatch(event: E): S {
const nextState = this.transitions[this.state][event.type];
if (nextState === undefined) {
throw new Error(`Invalid transition from ${this.state} on ${event.type}`);
}
this.state = nextState;
return this.state;
}
}
const manager = new StateManager<State, Event>('idle', machine);
manager.dispatch({ type: 'FETCH' }); // 'loading'
manager.dispatch({ type: 'SUCCESS', data: {} }); // 'success'Decorator Pattern
// Method decorators with type safety
function Log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
function Memoize(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const cache = new Map<string, any>();
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = originalMethod.apply(this, args);
cache.set(key, result);
return result;
};
return descriptor;
}
class Calculator {
@Log
@Memoize
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}Result/Either Pattern
// Type-safe error handling
type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
function ok<T>(value: T): Result<T, never> {
return { success: true, value };
}
function err<E>(error: E): Result<never, E> {
return { success: false, error };
}
async function fetchUser(id: string): Promise<Result<User, string>> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return err('User not found');
}
const user = await response.json();
return ok(user);
} catch (error) {
return err('Network error');
}
}
// Usage with pattern matching
const result = await fetchUser('123');
if (result.success) {
console.log(result.value.name); // Type-safe access
} else {
console.error(result.error); // Type-safe error
}
// Either monad
class Either<L, R> {
private constructor(
private readonly value: L | R,
private readonly isRight: boolean
) {}
static left<L, R>(value: L): Either<L, R> {
return new Either<L, R>(value, false);
}
static right<L, R>(value: R): Either<L, R> {
return new Either<L, R>(value, true);
}
map<T>(fn: (value: R) => T): Either<L, T> {
if (this.isRight) {
return Either.right(fn(this.value as R));
}
return Either.left(this.value as L);
}
flatMap<T>(fn: (value: R) => Either<L, T>): Either<L, T> {
if (this.isRight) {
return fn(this.value as R);
}
return Either.left(this.value as L);
}
getOrElse(defaultValue: R): R {
return this.isRight ? (this.value as R) : defaultValue;
}
}Singleton Pattern
// Type-safe singleton
class Database {
private static instance: Database;
private constructor() {
// Private constructor prevents instantiation
}
static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
query<T>(sql: string): Promise<T[]> {
// Execute query
return Promise.resolve([]);
}
}
const db = Database.getInstance();
// Generic singleton factory
function singleton<T>(factory: () => T): () => T {
let instance: T | undefined;
return () => {
if (!instance) {
instance = factory();
}
return instance;
};
}
const getConfig = singleton(() => ({
apiUrl: process.env.API_URL,
apiKey: process.env.API_KEY
}));Quick Reference
| Pattern | Use Case |
|---|---|
| Builder | Construct complex objects step by step |
| Factory | Create objects without specifying exact class |
| Repository | Abstract data access layer |
| API Client | Type-safe HTTP requests |
| State Machine | Manage state transitions |
| Decorator | Add behavior to methods |
| Result/Either | Type-safe error handling |
| Singleton | Ensure single instance |
| Query Builder | Type-safe database queries |
| Container | Dependency injection |
Type Guards and Narrowing
Type Predicates
// Basic type predicate
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processValue(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase()); // value is string
} else {
console.log(value.toFixed(2)); // value is number
}
}
// Generic type predicate
function isArray<T>(value: T | T[]): value is T[] {
return Array.isArray(value);
}
// Narrowing to specific interface
interface User {
type: 'user';
name: string;
email: string;
}
interface Admin {
type: 'admin';
name: string;
permissions: string[];
}
function isAdmin(account: User | Admin): account is Admin {
return account.type === 'admin';
}Discriminated Unions
// Tagged union pattern
type Result<T, E = Error> =
| { status: 'success'; data: T }
| { status: 'error'; error: E }
| { status: 'loading' };
function handleResult<T>(result: Result<T>) {
switch (result.status) {
case 'success':
console.log(result.data); // Narrowed to success
break;
case 'error':
console.error(result.error); // Narrowed to error
break;
case 'loading':
console.log('Loading...'); // Narrowed to loading
break;
}
}
// Complex discriminated union
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return (shape.base * shape.height) / 2;
}
}
// Exhaustive checking
function assertNever(x: never): never {
throw new Error('Unexpected value: ' + x);
}
function processShape(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return shape.radius;
case 'rectangle':
return shape.width;
case 'triangle':
return shape.base;
default:
return assertNever(shape); // Compile error if not exhaustive
}
}Built-in Type Guards
// typeof narrowing
function printValue(value: string | number | boolean) {
if (typeof value === 'string') {
console.log(value.toUpperCase());
} else if (typeof value === 'number') {
console.log(value.toFixed(2));
} else {
console.log(value ? 'yes' : 'no');
}
}
// instanceof narrowing
class Dog {
bark() { console.log('woof'); }
}
class Cat {
meow() { console.log('meow'); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
// in operator narrowing
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ('swim' in animal) {
animal.swim();
} else {
animal.fly();
}
}
// Truthiness narrowing
function printLength(value: string | null | undefined) {
if (value) {
console.log(value.length); // Narrowed to string
}
}
// Equality narrowing
function compare(x: string | number, y: string | boolean) {
if (x === y) {
// x and y are both string
console.log(x.toUpperCase(), y.toUpperCase());
}
}Assertion Functions
// Basic assertion function
function assert(condition: unknown, message?: string): asserts condition {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
function processUser(user: unknown) {
assert(typeof user === 'object' && user !== null);
assert('name' in user && typeof user.name === 'string');
console.log(user.name.toUpperCase()); // user is narrowed
}
// Type assertion function
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value is not a string');
}
}
function greet(name: unknown) {
assertIsString(name);
console.log(`Hello, ${name.toUpperCase()}`); // name is string
}
// Generic assertion function
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
if (value === null || value === undefined) {
throw new Error('Value is null or undefined');
}
}
function processValue(value: string | null) {
assertIsDefined(value);
console.log(value.length); // value is string
}
// Assert with type predicate
function assertIsUser(value: unknown): asserts value is User {
if (
typeof value !== 'object' ||
value === null ||
!('type' in value) ||
value.type !== 'user'
) {
throw new Error('Not a user');
}
}Control Flow Analysis
// Assignment narrowing
let x: string | number = Math.random() > 0.5 ? 'hello' : 42;
if (typeof x === 'string') {
x; // string
} else {
x; // number
}
// Return statement narrowing
function getValue(flag: boolean): string | number {
if (flag) {
return 'hello';
}
return 42; // TypeScript knows this must be number
}
// Throw statement narrowing
function processValue(value: string | null) {
if (!value) {
throw new Error('Value is required');
}
console.log(value.length); // value is string (null thrown above)
}
// Type guards in array methods
const mixed: (string | number)[] = ['a', 1, 'b', 2];
const strings = mixed.filter((x): x is string => typeof x === 'string');
// strings is string[]Branded Types
// Nominal typing with branded types
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, 'UserId'>;
type Email = Brand<string, 'Email'>;
type Url = Brand<string, 'Url'>;
// Constructor functions
function createUserId(id: string): UserId {
return id as UserId;
}
function createEmail(email: string): Email {
if (!email.includes('@')) {
throw new Error('Invalid email');
}
return email as Email;
}
// Usage prevents mixing
const userId: UserId = createUserId('user-123');
const email: Email = createEmail('user@example.com');
// const wrongAssignment: UserId = email; // Error!
// Type guard for branded types
function isUserId(value: string): value is UserId {
return /^user-\d+$/.test(value);
}
// Branded numbers
type Positive = Brand<number, 'Positive'>;
type Integer = Brand<number, 'Integer'>;
function createPositive(n: number): Positive {
if (n <= 0) throw new Error('Must be positive');
return n as Positive;
}
function createInteger(n: number): Integer {
if (!Number.isInteger(n)) throw new Error('Must be integer');
return n as Integer;
}Advanced Narrowing Patterns
// Array.isArray with generics
function processInput<T>(input: T | T[]): T[] {
return Array.isArray(input) ? input : [input];
}
// Object key narrowing
function getProperty<T extends object, K extends keyof T>(
obj: T,
key: K
): T[K] {
return obj[key];
}
// Mapped type narrowing
type Nullable<T> = { [K in keyof T]: T[K] | null };
function isComplete<T extends object>(
obj: Nullable<T>
): obj is T {
return Object.values(obj).every((v) => v !== null);
}
// Custom narrowing with type maps
type TypeMap = {
string: string;
number: number;
boolean: boolean;
};
function is<K extends keyof TypeMap>(
type: K,
value: unknown
): value is TypeMap[K] {
return typeof value === type;
}
if (is('string', someValue)) {
someValue.toUpperCase(); // someValue is string
}Quick Reference
| Pattern | Use Case |
|---|---|
value is Type | Type predicate function |
asserts condition | Assertion function |
asserts value is Type | Type assertion function |
| Discriminated union | Tagged union with literal type |
typeof guard | Primitive type checking |
instanceof guard | Class instance checking |
in operator | Property existence check |
assertNever | Exhaustive switch checking |
| Branded types | Nominal typing simulation |
NonNullable<T> | Remove null/undefined |
Utility Types
Built-in Utility Types
// Partial - All properties optional
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }
function updateUser(id: number, updates: Partial<User>) {
// Only pass fields to update
}
// Required - All properties required
type RequiredUser = Required<PartialUser>;
// { id: number; name: string; email: string; }
// Readonly - All properties readonly
type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string; }
// Pick - Select specific properties
type UserSummary = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// Omit - Exclude specific properties
type UserWithoutEmail = Omit<User, 'email'>;
// { id: number; name: string; }
// Record - Create object type with specific keys
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
// { [key: string]: 'admin' | 'user' | 'guest' }
type PageInfo = Record<'home' | 'about' | 'contact', { title: string }>;
// { home: { title: string }, about: { title: string }, contact: { title: string } }Type Extraction Utilities
// Extract - Extract types from union
type AllTypes = 'a' | 'b' | 'c' | 1 | 2 | 3;
type StringTypes = Extract<AllTypes, string>; // 'a' | 'b' | 'c'
type NumberTypes = Extract<AllTypes, number>; // 1 | 2 | 3
// Exclude - Remove types from union
type WithoutNumbers = Exclude<AllTypes, number>; // 'a' | 'b' | 'c'
// NonNullable - Remove null and undefined
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
// ReturnType - Extract function return type
function getUser() {
return { id: 1, name: 'John' };
}
type User = ReturnType<typeof getUser>; // { id: number; name: string }
// Parameters - Extract function parameter types
function createUser(name: string, age: number) {
return { name, age };
}
type CreateUserParams = Parameters<typeof createUser>; // [string, number]
// ConstructorParameters - Extract constructor parameters
class Point {
constructor(public x: number, public y: number) {}
}
type PointParams = ConstructorParameters<typeof Point>; // [number, number]
// InstanceType - Extract instance type from constructor
type PointInstance = InstanceType<typeof Point>; // PointCustom Utility Types
// DeepPartial - Recursive partial
type DeepPartial<T> = T extends object ? {
[K in keyof T]?: DeepPartial<T[K]>;
} : T;
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
}
type PartialConfig = DeepPartial<Config>;
// All nested properties are optional
// DeepReadonly - Recursive readonly
type DeepReadonly<T> = T extends object ? {
readonly [K in keyof T]: DeepReadonly<T[K]>;
} : T;
// Mutable - Remove readonly
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type MutableUser = Mutable<ReadonlyUser>;
// PickByType - Pick properties by value type
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Mixed {
id: number;
name: string;
age: number;
email: string;
}
type StringProps = PickByType<Mixed, string>; // { name: string; email: string }
type NumberProps = PickByType<Mixed, number>; // { id: number; age: number }
// OmitByType - Omit properties by value type
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K];
};
type NoStrings = OmitByType<Mixed, string>; // { id: number; age: number }Function Utilities
// Promisify - Convert sync to async
type Promisify<T extends (...args: any[]) => any> = (
...args: Parameters<T>
) => Promise<ReturnType<T>>;
function syncFunction(x: number): string {
return x.toString();
}
type AsyncVersion = Promisify<typeof syncFunction>;
// (x: number) => Promise<string>
// Awaited - Unwrap promise type
type AwaitedString = Awaited<Promise<string>>; // string
type DeepAwaited = Awaited<Promise<Promise<number>>>; // number
// ThisParameterType - Extract this parameter
function greet(this: User, message: string) {
return `${this.name}: ${message}`;
}
type ThisType = ThisParameterType<typeof greet>; // User
// OmitThisParameter - Remove this parameter
type GreetFunction = OmitThisParameter<typeof greet>;
// (message: string) => stringAdvanced Custom Utilities
// Nullable - Add null and undefined
type Nullable<T> = T | null | undefined;
// ValueOf - Get union of all property values
type ValueOf<T> = T[keyof T];
interface Codes {
success: 200;
notFound: 404;
error: 500;
}
type StatusCode = ValueOf<Codes>; // 200 | 404 | 500
// RequireAtLeastOne - Require at least one property
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
}[Keys];
interface Options {
id?: number;
name?: string;
email?: string;
}
type AtLeastOne = RequireAtLeastOne<Options>;
// Must have at least one of id, name, or email
// RequireOnlyOne - Require exactly one property
type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?:
Required<Pick<T, K>> &
Partial<Record<Exclude<Keys, K>, undefined>>;
}[Keys];
type OnlyOne = RequireOnlyOne<Options>;
// Must have exactly one of id, name, or email
// Merge - Deep merge two types
type Merge<T, U> = Omit<T, keyof U> & U;
interface Base {
id: number;
name: string;
}
interface Extension {
name: string; // Override
email: string; // Add
}
type Combined = Merge<Base, Extension>;
// { id: number; name: string; email: string }
// ConditionalKeys - Get keys matching condition
type ConditionalKeys<T, Condition> = {
[K in keyof T]: T[K] extends Condition ? K : never;
}[keyof T];
type FunctionKeys = ConditionalKeys<typeof Math, Function>;
// 'abs' | 'acos' | 'sin' | ...Tuple Utilities
// First - Get first element type
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type FirstType = First<[string, number, boolean]>; // string
// Last - Get last element type
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
type LastType = Last<[string, number, boolean]>; // boolean
// Tail - Remove first element
type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
type TailTypes = Tail<[string, number, boolean]>; // [number, boolean]
// Prepend - Add element to beginning
type Prepend<T extends any[], U> = [U, ...T];
type WithString = Prepend<[number, boolean], string>; // [string, number, boolean]
// Reverse - Reverse tuple
type Reverse<T extends any[]> =
T extends [infer First, ...infer Rest]
? [...Reverse<Rest>, First]
: [];
type Reversed = Reverse<[1, 2, 3]>; // [3, 2, 1]String Utilities
// Split - Split string into tuple
type Split<S extends string, D extends string> =
S extends `${infer T}${D}${infer U}`
? [T, ...Split<U, D>]
: [S];
type Parts = Split<'a-b-c', '-'>; // ['a', 'b', 'c']
// Join - Join tuple into string
type Join<T extends string[], D extends string> =
T extends [infer F extends string, ...infer R extends string[]]
? R extends []
? F
: `${F}${D}${Join<R, D>}`
: '';
type Joined = Join<['a', 'b', 'c'], '-'>; // 'a-b-c'
// Replace - Replace substring
type Replace<
S extends string,
From extends string,
To extends string
> = S extends `${infer L}${From}${infer R}`
? `${L}${To}${R}`
: S;
type Replaced = Replace<'hello world', 'world', 'TypeScript'>;
// 'hello TypeScript'
// TrimLeft - Remove leading whitespace
type TrimLeft<S extends string> =
S extends ` ${infer Rest}` ? TrimLeft<Rest> : S;
type Trimmed = TrimLeft<' hello'>; // 'hello'Quick Reference
| Utility | Purpose |
|---|---|
Partial<T> | Make all properties optional |
Required<T> | Make all properties required |
Readonly<T> | Make all properties readonly |
Pick<T, K> | Select subset of properties |
Omit<T, K> | Remove subset of properties |
Record<K, T> | Create object type with keys K |
Extract<T, U> | Extract types assignable to U |
Exclude<T, U> | Remove types assignable to U |
NonNullable<T> | Remove null and undefined |
ReturnType<T> | Extract function return type |
Parameters<T> | Extract function parameters |
Awaited<T> | Unwrap Promise type |
Related skills
How it compares
Pick typescript-pro over general coding skills when the blocker is TypeScript type design—generics, unions, and infer—not framework setup or lint configuration.
FAQ
When should I use branded types instead of enums?
Use branded types for domain modeling (UserId, OrderId) to prevent accidental id mix-ups at compile time. Avoid enums; use const objects with `as const` instead.
How do I ensure exhaustive handling of discriminated unions?
Use type guards with `state is SuccessState` predicates and exhaustive switch statements with `const _exhaustive: never = state` fallback to catch unhandled cases.
What tsconfig flags are required for strict type safety?
Enable strict mode plus noUncheckedIndexedAccess, noImplicitOverride, exactOptionalPropertyTypes, and skipLibCheck: false for full coverage.