| name | typescript-patterns-advanced |
| description | Advanced TypeScript — mapped types, template literal types, conditional types, infer, type guards, decorators, async patterns, testing with Vitest/Jest, and performance. Extends typescript-patterns. |
TypeScript Patterns — Advanced
This skill extends typescript-patterns with the type system internals, advanced generics, async patterns, testing, and runtime performance.
When to Activate
- Building type-safe library APIs with advanced generics
- Writing type-level transformations (mapped/conditional types)
- Testing TypeScript with Vitest or Jest
- Optimizing TypeScript compilation and bundle size
- Extracting route parameter names from URL patterns using template literal types and
infer
- Debugging slow TypeScript compilation caused by deep recursive conditional types
- Creating decorator-based patterns for logging, validation, or method interception in TypeScript 5+
Mapped Types
Transform one object type into another by iterating over keys:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface User {
id: string;
name: string;
email: string;
phone: string;
}
type UserUpdate = PartialBy<User, 'phone' | 'email'>;
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
type NonNullableProperties<T> = {
[K in keyof T]-?: NonNullable<T[K]>;
};
Template Literal Types
Build types from string patterns:
type EventName<Method extends string, Path extends string> =
`${Lowercase<Method>}:${Path}`;
type GetUsersEvent = EventName<'GET', '/users'>;
type CssValue = `${number}px` | `${number}%` | `${number}rem` | 'auto';
type CssProperty = 'margin' | 'padding' | 'fontSize';
type CssRule = `${CssProperty}: ${CssValue}`;
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<>
: T
?
: ;
= <>;
createRoute< >(
: ,
:
) { ... }
(, {
.(params.);
.(params.);
});
Conditional Types
Types that depend on other types:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type UnwrapArray<T> = T extends Array<infer U> ? U : T;
type ResolvedUser = UnwrapPromise<Promise<User>>;
type DeepUnwrap<T> = T extends Promise<infer U>
? DeepUnwrap<U>
: T extends Array<infer U>
? DeepUnwrap<U>[]
: T;
type MyReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
type IsString<T> = T extends string ? true : false;
type Test = IsString<string | number>;
<T> = [T] [] ? : ;
= < | >;
Type Guards and Narrowing
function format(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toFixed(2);
}
function handleError(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
typeof (value as User).id ===
);
}
(): {
(event. === ) {
.(event.);
}
}
(): asserts value is {
( value !== ) {
();
}
}
() {
(id);
id.();
}
Async Patterns
Type-Safe API Client
interface ApiClient {
get<T>(path: string): Promise<T>;
post<T, B = unknown>(path: string, body: B): Promise<T>;
put<T, B = unknown>(path: string, body: B): Promise<T>;
delete(path: string): Promise<void>;
}
type ApiResult<T> = Promise<Result<T, ApiError>>;
interface ApiError {
status: number;
message: string;
details?: Record<string, string[]>;
}
class HttpClient implements ApiClient {
constructor(private baseUrl: string) {}
async get<T>(path: string): Promise<T> {
const res = await fetch(``);
(!res.) (res., res.());
res.() <T>;
}
post<T, B = >(: , : B): <T> {
res = (, {
: ,
: { : },
: .(body),
});
(!res.) (res., res.());
res.() <T>;
}
}
Typed Event Emitter
type EventMap = {
'user:created': { user: User };
'user:deleted': { userId: string };
'order:placed': { order: Order };
'order:shipped': { orderId: string; trackingId: string };
};
class TypedEmitter<Events extends Record<string, unknown>> {
private listeners = new Map<string, Set<Function>>();
on<K extends keyof Events & string>(
event: K,
handler: (data: Events[K]) => void
): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.(handler);
..(event)?.(handler);
}
emit<K keyof & >(: K, : [K]): {
..(event)?.( (data));
}
}
emitter = <>();
emitter.(, .(user.));
emitter.(, { : someUser });
Typed Promise.all
async function fetchAll(userId: string, orderId: string) {
const [user, order] = await Promise.all([
fetchUser(userId),
fetchOrder(orderId),
]);
return { user, order };
}
Decorator Patterns (TypeScript 5+)
function log(target: unknown, context: ClassMethodDecoratorContext) {
const methodName = String(context.name);
return function (this: unknown, ...args: unknown[]) {
console.log(`Calling ${methodName}`, args);
const result = (target as Function).apply(this, args);
console.log(`${methodName} returned`, result);
return result;
};
}
function minLength(min: number) {
return function (value: undefined, context: ClassFieldDecoratorContext) {
return function (this: unknown, initialValue: string) {
if (initialValue. < min) {
();
}
initialValue;
};
};
}
{
(: , : ): {
{ : crypto.(), name, email, : () };
}
}
Testing with Vitest
Setup
npm install -D vitest @vitest/coverage-v8
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
thresholds: { lines: 80, functions: 80, branches: 80 },
},
},
});
Type-Safe Test Patterns
import { describe, it, expect, vi, beforeEach } from 'vitest';
describe('UserService', () => {
let mockRepo: ReturnType<typeof createMockRepo>;
beforeEach(() => {
mockRepo = createMockRepo();
});
it('creates a user with hashed password', async () => {
const service = new UserService(mockRepo);
const user = await service.create({ name: 'Alice', email: 'a@test.com' });
expect(user.id).toBeDefined();
expect(user.name).toBe('Alice');
expect(mockRepo.save).toHaveBeenCalledOnce();
});
it('throws on duplicate email', async () => {
mockRepo.findByEmail.mockResolvedValue({ id: '1', email: 'a@test.com' });
await expect(
new UserService(mockRepo).({ : , : })
)..();
});
});
(): jest.<> {
{
: vi.(),
: vi.(),
: vi.(),
: vi.(),
};
}
Testing Result Types
it('returns error result for invalid input', () => {
const result = parseUserInput({ name: 123 });
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.code).toBe('TYPE_ERROR');
expect(result.error.field).toBe('name');
}
});
it('returns ok result for valid input', () => {
const result = parseUserInput({ name: 'Alice', email: 'a@test.com' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.name).toBe('Alice');
}
});
Performance
Compilation Performance
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo",
"skipLibCheck": true,
"isolatedModules": true
}
}
Avoid Type-Level Recursion Depth
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
type DeepPartial<T, Depth extends number = 3> =
Depth extends 0
? T
: { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K], [-1, 0, 1, 2][Depth]> : T[K] };
Quick Reference
| Feature | Usage |
|---|
| Mapped type | { [K in keyof T]: ... } |
| Template literal | `${Prefix}${string}` |
| Conditional type | T extends X ? A : B |
| Infer | T extends Promise<infer U> ? U : never |
| Type predicate | function isUser(x): x is User |
| Assertion function | function assert(x): asserts x is T |
| Distributive | T extends X ? A : B distributes over unions |
| Non-distributive | [T] extends [X] ? A : B |
satisfies | const config = { ... } satisfies Config |
const type param | function id<const T>(x: T): T (TS 5.0) |