Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:22:35 +08:00
commit 6fcffca9b0
35 changed files with 8235 additions and 0 deletions

View File

@@ -0,0 +1,440 @@
---
name: using-generics
description: 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.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Task, TodoWrite
version: 1.0.0
---
<role>
This skill teaches how to use TypeScript generics effectively with proper constraints, avoiding `any` defaults, and leveraging mapped types for type transformations.
</role>
<when-to-activate>
This skill activates when:
- Creating reusable functions or classes
- Designing generic APIs or libraries
- Working with generic defaults (`<T = ...>`)
- Implementing mapped types or conditional types
- User mentions generics, type parameters, constraints, or reusable types
</when-to-activate>
<overview>
Generics enable writing reusable code that works with multiple types while preserving type safety. Proper use of constraints prevents `any` abuse and provides better IDE support.
**Key Concepts**:
1. **Generic Parameters**: `<T>` - Type variables that get filled in at call site
2. **Constraints**: `<T extends Shape>` - Limits what types T can be
3. **Defaults**: `<T = string>` - Fallback when type not provided
4. **Mapped Types**: Transform existing types systematically
**Impact**: Write flexible, reusable code without sacrificing type safety.
</overview>
<workflow>
## Generic Design Flow
**Step 1: Identify the Varying Type**
What changes between uses?
- Data type in container (Array<T>, Promise<T>)
- Object shape variations
- Return type based on input
- Multiple related types
**Step 2: Choose Constraint Strategy**
**No Constraint** - Accepts any type
```typescript
function identity<T>(value: T): T {
return value;
}
```
**Extends Constraint** - Requires specific shape
```typescript
function logId<T extends { id: string }>(item: T): void {
console.log(item.id);
}
```
**Union Constraint** - Limited set of types
```typescript
function process<T extends string | number>(value: T): T {
return value;
}
```
**Multiple Constraints** - Multiple type parameters with relationships
```typescript
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:
```typescript
interface ApiResponse<T = unknown> { data: T; }
```
Or require explicit type parameter:
```typescript
interface ApiResponse<T> { data: T; }
```
</workflow>
<examples>
## Example 1: Generic Function Constraints
**❌ No constraint (too permissive)**
```typescript
function getProperty<T>(obj: T, key: string): any {
return obj[key];
}
```
**Problems**:
- `obj[key]` not type-safe (T might not have string keys)
- Returns `any` (loses type information)
- No IDE autocomplete for key
**✅ Proper constraints**
```typescript
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**:
- Type-safe key access
- Return type is `T[K]` (actual property type)
- IDE autocompletes valid keys
- Compile error for invalid keys
---
## Example 2: Generic Defaults
**❌ Using `any` default (unsafe)**
```typescript
interface Result<T = any> {
data: T;
error?: string;
}
const result: Result = { data: "anything" };
result.data.nonExistentProperty;
```
**✅ Using `unknown` default (safe)**
```typescript
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)**
```typescript
interface Result<T> {
data: T;
error?: string;
}
const result: Result<string> = { data: "specific type" };
console.log(result.data.toUpperCase());
```
---
## Example 3: Constraining Generic Parameters
**Example: Ensuring object has id**
```typescript
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**
```typescript
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**
```typescript
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = firstElement([1, 2, 3]);
const second = firstElement(["a", "b"]);
```
---
## Example 4: Multiple Type Parameters
**Example: Key-value mapping**
```typescript
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**
```typescript
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");
```
---
## Example 5: Mapped Types
**Making properties optional:**
```typescript
type Partial<T> = {
[P in keyof T]?: T[P];
};
const partialUser: Partial<User> = { name: "Alice" };
```
**Making properties readonly:**
```typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
```
**Picking specific properties:**
```typescript
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.
---
## Example 6: Conditional Types
**Unwrap promise type:**
```typescript
type Awaited<T> = T extends Promise<infer U> ? U : T;
```
**Extract function parameters:**
```typescript
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>
<progressive-disclosure>
## Reference Files
**In this skill:**
- `references/detailed-examples.md` - DeepPartial, FilterByType, conditional types, constructables
- `references/common-patterns.md` - Array ops, object utils, Promise utils, builders
- `references/advanced-patterns.md` - Recursive generics, variadic tuples, branded types, HKTs
**Related skills:**
- Use the using-type-guards skill for narrowing generic types
- Use the avoiding-any-types skill for generic defaults
- Use the using-runtime-checks skill for validating generic data
</progressive-disclosure>
<constraints>
**MUST:**
- Use `extends` to constrain generic parameters when accessing properties
- Use `keyof T` for type-safe property access
- Use `unknown` for generic defaults if truly dynamic
- Specify return type based on generic parameters
**SHOULD:**
- Prefer no default over `any` default
- Use descriptive type parameter names for complex generics
- Infer type parameters from usage when possible
- Use helper types (Pick, Omit, Partial) over manual mapping
**NEVER:**
- Use `any` as generic default
- Access properties on unconstrained generics
- Use `as any` to bypass generic constraints
- Create overly complex nested generics (split into smaller types)
</constraints>
<patterns>
## Common Generic Patterns
### Array Operations
```typescript
function 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;
}
```
### Object Utilities
```typescript
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 Generics
```typescript
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>
<validation>
## Generic Type Safety Checklist
1. **Constraints**:
- [ ] Generic parameters constrained when accessing properties
- [ ] `keyof` used for property key types
- [ ] `extends` used appropriately
2. **Defaults**:
- [ ] No `any` defaults
- [ ] `unknown` used for truly dynamic defaults
- [ ] Or no default (require explicit type)
3. **Type Inference**:
- [ ] Type parameters inferred from usage
- [ ] Explicit types only when inference fails
- [ ] Return types correctly derived from generics
4. **Complexity**:
- [ ] Generic types are understandable
- [ ] Complex types split into smaller pieces
- [ ] Helper types used appropriately
</validation>
<advanced-patterns>
## Advanced Generic Patterns
For advanced patterns including:
- **Recursive Generics** (DeepReadonly, DeepPartial)
- **Variadic Tuple Types** (type-safe array concatenation)
- **Template Literal Types** (string manipulation at type level)
- **Branded Types** (nominal typing in structural system)
- **Distributive Conditional Types**
- **Higher-Kinded Types** (simulation)
See `references/advanced-patterns.md` for detailed implementations and examples.
</advanced-patterns>

View File

@@ -0,0 +1,168 @@
# Advanced Generic Patterns
## Recursive Generics
Use recursive generics to apply transformations deeply through nested object structures:
```typescript
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
interface Config {
database: {
host: string;
credentials: {
username: string;
password: string;
};
};
}
const config: DeepReadonly<Config> = {
database: {
host: "localhost",
credentials: {
username: "admin",
password: "secret"
}
}
};
```
## Variadic Tuple Types
TypeScript 4.0+ supports variadic tuple types for precise array concatenation:
```typescript
function concat<T extends readonly unknown[], U extends readonly unknown[]>(
arr1: T,
arr2: U
): [...T, ...U] {
return [...arr1, ...arr2];
}
const result = concat([1, 2], ["a", "b"]);
function curry<T extends unknown[], U extends unknown[], R>(
fn: (...args: [...T, ...U]) => R,
...first: T
): (...args: U) => R {
return (...rest: U) => fn(...first, ...rest);
}
```
## Template Literal Types
Combine string literals with generics for type-safe string manipulation:
```typescript
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">;
type HoverEvent = EventName<"hover">;
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint<M extends HTTPMethod, P extends string> = `${M} ${P}`;
type UserEndpoint = Endpoint<"GET", "/users/:id">;
```
## Branded Types
Create nominal types in TypeScript's structural type system:
```typescript
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, "UserId">;
type EmailAddress = Brand<string, "Email">;
type OrderId = Brand<string, "OrderId">;
function sendEmail(to: EmailAddress, from: EmailAddress): void {
console.log(`Sending email from ${from} to ${to}`);
}
function getUser(id: UserId): void {
console.log(`Fetching user ${id}`);
}
const email = "user@example.com" as EmailAddress;
const userId = "user-123" as UserId;
sendEmail(email, email);
getUser(userId);
```
## Distributive Conditional Types
Conditional types distribute over unions when the checked type is a naked type parameter:
```typescript
type ToArray<T> = T extends any ? T[] : never;
type Nums = ToArray<number | string>;
type NonNullable<T> = T extends null | undefined ? never : T;
type SafeString = NonNullable<string | null | undefined>;
```
## Mapped Type Modifiers
Use `+`, `-`, `readonly`, and `?` modifiers in mapped types:
```typescript
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type ReadonlyPartial<T> = {
+readonly [P in keyof T]+?: T[P];
};
```
## Inference in Conditional Types
Use `infer` to extract types within conditional types:
```typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type PromiseValue<T> = T extends Promise<infer V> ? V : T;
type FirstParameter<T> = T extends (first: infer F, ...args: any[]) => any
? F
: never;
```
## Higher-Kinded Types (Simulation)
While TypeScript doesn't have true HKTs, you can simulate them:
```typescript
interface Functor<F> {
map<A, B>(fa: HKT<F, A>, f: (a: A) => B): HKT<F, B>;
}
interface HKT<F, A> {
_F: F;
_A: A;
}
interface ArrayF {}
type ArrayHKT<A> = HKT<ArrayF, A> & A[];
const arrayFunctor: Functor<ArrayF> = {
map: (fa, f) => fa.map(f)
};
```

View File

@@ -0,0 +1,332 @@
# Common Generic Patterns
## Array Operations
Generic array utilities maintain type safety while providing reusable functionality:
```typescript
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
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 flatten<T>(arr: T[][]): T[] {
return arr.reduce((acc, item) => acc.concat(item), []);
}
function unique<T>(arr: T[]): T[] {
return Array.from(new Set(arr));
}
function partition<T>(arr: T[], predicate: (item: T) => boolean): [T[], T[]] {
const truthy: T[] = [];
const falsy: T[] = [];
for (const item of arr) {
if (predicate(item)) {
truthy.push(item);
} else {
falsy.push(item);
}
}
return [truthy, falsy];
}
```
## Object Utilities
Type-safe object manipulation:
```typescript
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;
}
function omit<T extends object, K extends keyof T>(
obj: T,
...keys: K[]
): Omit<T, K> {
const result = { ...obj };
for (const key of keys) {
delete result[key];
}
return result as Omit<T, K>;
}
function keys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
function values<T extends object>(obj: T): T[keyof T][] {
return Object.values(obj) as T[keyof T][];
}
function entries<T extends object>(obj: T): [keyof T, T[keyof T]][] {
return Object.entries(obj) as [keyof T, T[keyof T]][];
}
function mapValues<T extends object, U>(
obj: T,
fn: (value: T[keyof T], key: keyof T) => U
): Record<keyof T, U> {
const result = {} as Record<keyof T, U>;
for (const key in obj) {
result[key] = fn(obj[key], key);
}
return result;
}
```
## Promise Utilities
Generic async operations:
```typescript
async function retry<T>(
fn: () => Promise<T>,
maxAttempts: number
): Promise<T> {
let lastError: Error;
for (let i = 0; i < maxAttempts; i++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
}
}
throw lastError!;
}
async function timeout<T>(
promise: Promise<T>,
ms: number
): Promise<T> {
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), ms);
});
return Promise.race([promise, timeoutPromise]);
}
async function sequence<T>(
promises: (() => Promise<T>)[]
): Promise<T[]> {
const results: T[] = [];
for (const promiseFn of promises) {
results.push(await promiseFn());
}
return results;
}
```
## Class Generics
Generic classes for reusable data structures:
```typescript
class Container<T> {
constructor(private value: T) {}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
map<U>(fn: (value: T) => U): Container<U> {
return new Container(fn(this.value));
}
flatMap<U>(fn: (value: T) => Container<U>): Container<U> {
return fn(this.value);
}
}
class Result<T, E = Error> {
private constructor(
private readonly value?: T,
private readonly error?: E
) {}
static ok<T, E = Error>(value: T): Result<T, E> {
return new Result(value, undefined);
}
static err<T, E = Error>(error: E): Result<T, E> {
return new Result(undefined, error);
}
isOk(): this is Result<T, never> {
return this.value !== undefined;
}
isErr(): this is Result<never, E> {
return this.error !== undefined;
}
unwrap(): T {
if (this.error !== undefined) {
throw this.error;
}
return this.value!;
}
map<U>(fn: (value: T) => U): Result<U, E> {
if (this.error !== undefined) {
return Result.err(this.error);
}
return Result.ok(fn(this.value!));
}
}
```
## Builder Pattern
Fluent API with type safety:
```typescript
class QueryBuilder<T extends object> {
private filters: Array<(item: T) => boolean> = [];
private sortFn?: (a: T, b: T) => number;
private limitValue?: number;
where<K extends keyof T>(key: K, value: T[K]): this {
this.filters.push(item => item[key] === value);
return this;
}
whereFn(predicate: (item: T) => boolean): this {
this.filters.push(predicate);
return this;
}
sort<K extends keyof T>(key: K, direction: "asc" | "desc" = "asc"): this {
this.sortFn = (a, b) => {
const aVal = a[key];
const bVal = b[key];
if (aVal < bVal) return direction === "asc" ? -1 : 1;
if (aVal > bVal) return direction === "asc" ? 1 : -1;
return 0;
};
return this;
}
limit(n: number): this {
this.limitValue = n;
return this;
}
execute(data: T[]): T[] {
let result = data.filter(item =>
this.filters.every(filter => filter(item))
);
if (this.sortFn) {
result = result.sort(this.sortFn);
}
if (this.limitValue !== undefined) {
result = result.slice(0, this.limitValue);
}
return result;
}
}
const users = [
{ id: 1, name: "Alice", active: true, age: 30 },
{ id: 2, name: "Bob", active: false, age: 25 },
{ id: 3, name: "Charlie", active: true, age: 35 }
];
const active = new QueryBuilder<typeof users[0]>()
.where("active", true)
.sort("age", "desc")
.limit(5)
.execute(users);
```
## Event Emitter Pattern
Type-safe event handling:
```typescript
type EventMap = Record<string, any>;
class TypedEventEmitter<Events extends EventMap> {
private listeners: {
[K in keyof Events]?: Array<(data: Events[K]) => void>;
} = {};
on<K extends keyof Events>(
event: K,
listener: (data: Events[K]) => void
): this {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(listener);
return this;
}
emit<K extends keyof Events>(event: K, data: Events[K]): void {
const listeners = this.listeners[event];
if (listeners) {
for (const listener of listeners) {
listener(data);
}
}
}
off<K extends keyof Events>(
event: K,
listener: (data: Events[K]) => void
): this {
const listeners = this.listeners[event];
if (listeners) {
this.listeners[event] = listeners.filter(l => l !== listener);
}
return this;
}
}
interface AppEvents {
userLogin: { userId: string; timestamp: number };
userLogout: { userId: string };
dataUpdated: { id: string; changes: Record<string, unknown> };
}
const emitter = new TypedEventEmitter<AppEvents>();
emitter.on("userLogin", (data) => {
console.log(`User ${data.userId} logged in at ${data.timestamp}`);
});
```

View File

@@ -0,0 +1,305 @@
# Detailed Generic Examples
## Deep Partial Implementation
The `DeepPartial` type recursively makes all properties optional, including nested objects:
```typescript
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface DatabaseConfig {
host: string;
port: number;
credentials: {
username: string;
password: string;
ssl: {
enabled: boolean;
cert: string;
};
};
}
const partialConfig: DeepPartial<DatabaseConfig> = {
host: "localhost",
credentials: {
username: "admin",
ssl: {
enabled: true
}
}
};
function mergeConfig(
defaults: DatabaseConfig,
override: DeepPartial<DatabaseConfig>
): DatabaseConfig {
return {
...defaults,
...override,
credentials: {
...defaults.credentials,
...override.credentials,
ssl: {
...defaults.credentials.ssl,
...override.credentials?.ssl
}
}
};
}
```
## Conditional Return Types
Use conditional types to change return types based on input:
```typescript
type ParseResult<T extends "json" | "text"> =
T extends "json" ? unknown : string;
function parse<T extends "json" | "text">(
response: Response,
type: T
): Promise<ParseResult<T>> {
if (type === "json") {
return response.json() as Promise<ParseResult<T>>;
}
return response.text() as Promise<ParseResult<T>>;
}
async function example() {
const json = await parse(response, "json");
const text = await parse(response, "text");
}
type QueryResult<T extends boolean> = T extends true
? Array<{ id: string; data: unknown }>
: { id: string; data: unknown } | undefined;
function query<Multiple extends boolean = false>(
sql: string,
multiple?: Multiple
): QueryResult<Multiple> {
if (multiple) {
return [] as QueryResult<Multiple>;
}
return undefined as QueryResult<Multiple>;
}
const single = query("SELECT * FROM users WHERE id = 1");
const many = query("SELECT * FROM users", true);
```
## Filter by Type Pattern
Extract only properties of a specific type from an object:
```typescript
type FilterByType<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
interface User {
id: string;
name: string;
age: number;
active: boolean;
createdAt: Date;
score: number;
tags: string[];
}
type StringProps = FilterByType<User, string>;
type NumberProps = FilterByType<User, number>;
type FilterByValueType<T, U> = Pick<
T,
{
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T]
>;
type BooleanProps = FilterByValueType<User, boolean>;
```
## Unwrap Nested Promises
Extract the final resolved type from nested Promises:
```typescript
type Awaited<T> = T extends Promise<infer U>
? Awaited<U>
: T;
type A = Awaited<Promise<string>>;
type B = Awaited<Promise<Promise<number>>>;
type C = Awaited<Promise<Promise<Promise<boolean>>>>;
async function deepFetch(): Promise<Promise<{ data: string }>> {
return Promise.resolve(
Promise.resolve({ data: "nested" })
);
}
type DeepFetchResult = Awaited<ReturnType<typeof deepFetch>>;
```
## Extract Function Parameters
Get parameter types from function signatures:
```typescript
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function processUser(id: string, name: string, age: number): void {
console.log(id, name, age);
}
type ProcessUserParams = Parameters<typeof processUser>;
function callWithParams<F extends (...args: any[]) => any>(
fn: F,
...args: Parameters<F>
): ReturnType<F> {
return fn(...args);
}
callWithParams(processUser, "123", "Alice", 30);
type FirstParameter<T> = T extends (first: infer F, ...args: any[]) => any
? F
: never;
type FirstParam = FirstParameter<typeof processUser>;
```
## Constructable Types
Work with classes and constructors generically:
```typescript
interface Constructable<T> {
new (...args: any[]): T;
}
function create<T>(Constructor: Constructable<T>): T {
return new Constructor();
}
function createWithArgs<T, Args extends any[]>(
Constructor: new (...args: Args) => T,
...args: Args
): T {
return new Constructor(...args);
}
class User {
constructor(public name: string, public age: number) {}
}
const user1 = create(User);
const user2 = createWithArgs(User, "Alice", 30);
function inject<T>(
Constructor: Constructable<T>,
dependencies: Map<Constructable<any>, any>
): T {
const args = getDependencies(Constructor, dependencies);
return new Constructor(...args);
}
```
## Mapped Type Transformations
Complex property transformations using mapped types:
```typescript
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type PersonSetters = Setters<Person>;
type ReadonlyKeys<T> = {
[K in keyof T]-?: T[K] extends { readonly [key: string]: any }
? K
: never;
}[keyof T];
type WritableKeys<T> = {
[K in keyof T]-?: T[K] extends { readonly [key: string]: any }
? never
: K;
}[keyof T];
```
## Type-Safe Event Handlers
Generic event handler system with strict typing:
```typescript
interface EventHandlers<T> {
onCreate?: (item: T) => void;
onUpdate?: (item: T, changes: Partial<T>) => void;
onDelete?: (id: string) => void;
}
class Store<T extends { id: string }> {
private items = new Map<string, T>();
private handlers: EventHandlers<T> = {};
setHandlers(handlers: EventHandlers<T>): void {
this.handlers = handlers;
}
create(item: T): void {
this.items.set(item.id, item);
this.handlers.onCreate?.(item);
}
update(id: string, changes: Partial<T>): void {
const item = this.items.get(id);
if (item) {
const updated = { ...item, ...changes };
this.items.set(id, updated);
this.handlers.onUpdate?.(updated, changes);
}
}
delete(id: string): void {
this.items.delete(id);
this.handlers.onDelete?.(id);
}
}
interface Todo {
id: string;
title: string;
completed: boolean;
}
const todoStore = new Store<Todo>();
todoStore.setHandlers({
onCreate: (todo) => console.log("Created:", todo.title),
onUpdate: (todo, changes) => console.log("Updated:", changes),
onDelete: (id) => console.log("Deleted:", id)
});
```