Teaches generic constraints, avoiding any in generic defaults, and mapped types in TypeScript. Use when creating reusable functions, components, or types that work with multiple types while maintaining type safety.
Teaches generic constraints, avoiding any in generic defaults, and mapped types in TypeScript. Use when creating reusable functions, components, or types that work with multiple types while maintaining type safety.
/plugin marketplace add djankies/claude-configs/plugin install typescript@claude-configsThis skill is limited to using the following tools:
references/advanced-patterns.mdreferences/common-patterns.mdreferences/detailed-examples.md<T = ...>)Key Concepts:
<T> - Type variables that get filled in at call site<T extends Shape> - Limits what types T can be<T = string> - Fallback when type not providedImpact: Write flexible, reusable code without sacrificing type safety. </overview>
<workflow> ## Generic Design FlowStep 1: Identify the Varying Type
What changes between uses?
Step 2: Choose Constraint Strategy
No Constraint - Accepts any type
function identity<T>(value: T): T {
return value;
}
Extends Constraint - Requires specific shape
function logId<T extends { id: string }>(item: T): void {
console.log(item.id);
}
Union Constraint - Limited set of types
function process<T extends string | number>(value: T): T {
return value;
}
Multiple Constraints - Multiple type parameters with relationships
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
Step 3: Set Default (If Needed)
Prefer no default over any default:
interface ApiResponse<T = unknown> { data: T; }
Or require explicit type parameter:
interface ApiResponse<T> { data: T; }
</workflow>
<examples>
## Example 1: Generic Function Constraints
❌ No constraint (too permissive)
function getProperty<T>(obj: T, key: string): any {
return obj[key];
}
Problems:
obj[key] not type-safe (T might not have string keys)any (loses type information)✅ Proper constraints
function getProperty<T extends object, K extends keyof T>(
obj: T,
key: K
): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name");
const invalid = getProperty(user, "invalid");
Benefits:
T[K] (actual property type)❌ Using any default (unsafe)
interface Result<T = any> {
data: T;
error?: string;
}
const result: Result = { data: "anything" };
result.data.nonExistentProperty;
✅ Using unknown default (safe)
interface Result<T = unknown> {
data: T;
error?: string;
}
const result: Result = { data: "anything" };
if (typeof result.data === "string") {
console.log(result.data.toUpperCase());
}
✅ No default (best)
interface Result<T> {
data: T;
error?: string;
}
const result: Result<string> = { data: "specific type" };
console.log(result.data.toUpperCase());
Example: Ensuring object has id
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
const users = [
{ id: "1", name: "Alice" },
{ id: "2", name: "Bob" }
];
const user = findById(users, "1");
Example: Ensuring constructable type
interface Constructable<T> {
new (...args: any[]): T;
}
function create<T>(Constructor: Constructable<T>): T {
return new Constructor();
}
class User {
name = "Anonymous";
}
const user = create(User);
Example: Ensuring array element type
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = firstElement([1, 2, 3]);
const second = firstElement(["a", "b"]);
Example: Key-value mapping
function mapObject<T extends object, U>(
obj: T,
fn: (value: T[keyof T]) => U
): Record<keyof T, U> {
const result = {} as Record<keyof T, U>;
for (const key in obj) {
result[key] = fn(obj[key]);
}
return result;
}
const user = { name: "Alice", age: 30 };
const lengths = mapObject(user, val => String(val).length);
Example: Conditional return types
function parse<T extends "json" | "text">(
response: Response,
type: T
): T extends "json" ? Promise<unknown> : Promise<string> {
if (type === "json") {
return response.json() as any;
}
return response.text() as any;
}
const json = await parse(response, "json");
const text = await parse(response, "text");
Making properties optional:
type Partial<T> = {
[P in keyof T]?: T[P];
};
const partialUser: Partial<User> = { name: "Alice" };
Making properties readonly:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Picking specific properties:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type UserPreview = Pick<User, "id" | "name">;
See references/detailed-examples.md for DeepPartial, FilterByType, and other complex mapped type patterns.
Unwrap promise type:
type Awaited<T> = T extends Promise<infer U> ? U : T;
Extract function parameters:
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
See references/detailed-examples.md for more conditional type patterns including FilterByType, nested promise unwrapping, and parameter extraction.
</examples>
In this skill:
references/detailed-examples.md - DeepPartial, FilterByType, conditional types, constructablesreferences/common-patterns.md - Array ops, object utils, Promise utils, buildersreferences/advanced-patterns.md - Recursive generics, variadic tuples, branded types, HKTsRelated skills:
extends to constrain generic parameters when accessing propertieskeyof T for type-safe property accessunknown for generic defaults if truly dynamicSHOULD:
any defaultNEVER:
any as generic defaultas any to bypass generic constraintsfunction last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
function chunk<T>(arr: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
function pick<T extends object, K extends keyof T>(
obj: T,
...keys: K[]
): Pick<T, K> {
const result = {} as Pick<T, K>;
for (const key of keys) {
result[key] = obj[key];
}
return result;
}
class Container<T> {
constructor(private value: T) {}
map<U>(fn: (value: T) => U): Container<U> {
return new Container(fn(this.value));
}
}
See references/common-patterns.md for complete implementations including Promise utilities, builders, event emitters, and more.
</patterns>
Constraints:
keyof used for property key typesextends used appropriatelyDefaults:
any defaultsunknown used for truly dynamic defaultsType Inference:
Complexity:
For advanced patterns including:
See references/advanced-patterns.md for detailed implementations and examples.
</advanced-patterns>
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.