TypeScript design patterns that use the type system to enforce correctness at compile time — the builder pattern with required-field tracking, type-safe state machines and event emitters, dependency injection, plugin systems, recursive/deep-readonly utility types, type-safe module encapsulation, advanced generic constraints, and typed API clients. Use when structuring a class or module so invalid usage fails to compile rather than throwing at runtime, e.g. "how do I stop `.build()` being called before required fields are set" or "how do I type an event emitter so payloads match their event name".
67
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Structural patterns that push correctness into the type system: a class or module shaped so the compiler rejects a misuse the runtime would otherwise have to catch.
A pattern here earns its place only if it converts a runtime failure into a compile error. Before reaching for one, ask what invalid usage it should make impossible to write, not just cleaner to read — a builder that still lets .build() run with missing fields is a fluent API, not a type-safe one. Prefer composing the type system's own primitives (discriminated unions, conditional types, mapped types) over hand-rolled runtime checks wherever the invalid state can be excluded at the type level instead of merely caught. Treat generics as free specificity, not decoration: a generic parameter with no constraint is barely more useful than any, and a pattern that leaves one unconstrained has not finished its job.
Use this skill when:
DeepReadonly, DeepPartial, tree structures).build() from compiling until every required field is set?"DeepReadonly, DeepPartial, tree/path types).typescript-type-guards skill.typescript-type-system skill.Partial, Pick, ReturnType) — see the sibling typescript-utility-types skill.Do not use this skill to look up how a conditional type, mapped type, or discriminated union actually works at the type level — the pattern files assume that mechanic and build on top of it. Do not use it as a substitute for tsc --noEmit — a pattern that looks type-safe in a reference file still needs the compiler to confirm it holds for your actual types.
npx tsc --noEmit.build() compile with missing required fieldsWHY: a builder pattern's entire value is catching an incomplete construction at compile time; if .build() accepts an incomplete state, it is no safer than a plain object literal.
BAD:
class UserBuilder {
private name?: string;
setName(name: string) { this.name = name; return this; }
build() { return { name: this.name }; } // name may be undefined — compiles anyway
}GOOD:
class UserBuilder<HasName extends boolean = false> {
private name?: string;
setName(name: string): UserBuilder<true> {
this.name = name;
return this as UserBuilder<true>;
}
build(this: UserBuilder<true>): { name: string } {
return { name: this.name! };
}
}WHY: an unconstrained <T> gives the compiler nothing to check, so the pattern degrades to any the moment a caller passes something unexpected.
BAD:
function createInstance<T>(ctor: T, ...args: any[]) {
return new (ctor as any)(...args);
}GOOD:
function createInstance<T extends new (...args: any[]) => any>(
ctor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new ctor(...args);
}emit/on with a loose string and any[]WHY: the whole point of a typed event emitter is that an event name and its payload are checked together; emit(event: string, ...args: any[]) throws that guarantee away.
BAD:
class EventEmitter {
emit(event: string, ...args: any[]): void { /* ... */ }
on(event: string, handler: (...args: any[]) => void): void { /* ... */ }
}GOOD:
class EventEmitter<T extends Record<string, (...args: any[]) => void>> {
emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void { /* ... */ }
on<K extends keyof T>(event: K, handler: T[K]): void { /* ... */ }
}| File | Covers |
|---|---|
references/builder.md | Compile-time enforcement of required fields in the builder pattern |
references/state-machine.md | Discriminated-union state machines with compile-time transition validation |
references/event-emitter.md | Type-safe event names and payloads |
references/dependency-injection.md | Compile-time-verified dependency wiring |
references/plugin-system.md | Extensible, typed plugin architectures |
references/deep-readonly.md | DeepReadonly, DeepPartial, DeepRequired, DeepMutable, runtime deepFreeze |
references/recursive-types.md | Self-referential types: JSON values, path types, tree structures |
references/type-safe-module.md | Encapsulating module internals behind a typed public surface |
references/api-client.md | Typed API clients built from a central route configuration |
references/form-validation.md | Type-safe, cross-field-aware form validation (sync and async validators) |
references/branded-types.md | Nominal typing with Zod-based validated brands and domain-primitive use cases |
references/advanced-generics.md | Complex, composable generic constraint patterns |
a1083f4
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.