Rename project from Gooti to Plebian Signer and add Claude Code config

- Rename all gooti-* files to plebian-signer-* across Chrome and Firefox
- Rename GootiMetaHandler to SignerMetaHandler in common library
- Update all references to use new naming convention
- Add CLAUDE.md with project build/architecture documentation
- Add Claude Code release command tailored for this npm/Angular project
- Add NWC-IMPLEMENTATION.md design document
- Add Claude skills for nostr, typescript, react, svelte, and applesauce libs
- Update README and various component templates with new branding

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-17 09:29:00 +01:00
parent e11ca7a0d2
commit 3c63e6555c
88 changed files with 19449 additions and 498 deletions

View File

@@ -0,0 +1,133 @@
# TypeScript Claude Skill
Comprehensive TypeScript skill for type-safe development with modern JavaScript/TypeScript applications.
## Overview
This skill provides in-depth knowledge about TypeScript's type system, patterns, best practices, and integration with popular frameworks like React. It covers everything from basic types to advanced type manipulation techniques.
## Files
### Core Documentation
- **SKILL.md** - Main skill file with workflows and when to use this skill
- **quick-reference.md** - Quick lookup guide for common TypeScript syntax and patterns
### Reference Materials
- **references/type-system.md** - Comprehensive guide to TypeScript's type system
- **references/utility-types.md** - Complete reference for built-in and custom utility types
- **references/common-patterns.md** - Real-world TypeScript patterns and idioms
### Examples
- **examples/type-system-basics.ts** - Fundamental TypeScript concepts
- **examples/advanced-types.ts** - Generics, conditional types, mapped types
- **examples/react-patterns.ts** - Type-safe React components and hooks
- **examples/README.md** - Guide to using the examples
## Usage
### When to Use This Skill
Reference this skill when:
- Writing or refactoring TypeScript code
- Designing type-safe APIs and interfaces
- Working with advanced type system features
- Configuring TypeScript projects
- Troubleshooting type errors
- Implementing type-safe patterns with libraries
- Converting JavaScript to TypeScript
### Quick Start
For quick lookups, start with `quick-reference.md` which provides concise syntax and patterns.
For learning or deep dives:
1. **Fundamentals**: Start with `references/type-system.md`
2. **Utilities**: Learn about transformations in `references/utility-types.md`
3. **Patterns**: Study real-world patterns in `references/common-patterns.md`
4. **Practice**: Explore code examples in `examples/`
## Key Topics Covered
### Type System
- Primitive types and special types
- Object types (interfaces, type aliases)
- Union and intersection types
- Literal types and template literal types
- Type inference and narrowing
- Generic types with constraints
- Conditional types and mapped types
- Recursive types
### Advanced Features
- Type guards and type predicates
- Assertion functions
- Branded types for nominal typing
- Key remapping and filtering
- Distributive conditional types
- Type-level programming
### Utility Types
- Built-in utilities (Partial, Pick, Omit, etc.)
- Custom utility type patterns
- Deep transformations
- Type composition
### React Integration
- Component props typing
- Generic components
- Hooks with TypeScript
- Context with type safety
- Event handlers
- Ref typing
### Best Practices
- Type safety patterns
- Error handling
- Code organization
- Integration with Zod for runtime validation
- Named return variables (Go-style)
- Discriminated unions for state management
## Integration with Project Stack
This skill is designed to work seamlessly with:
- **React 19**: Type-safe component development
- **TanStack Ecosystem**: Typed queries, routing, forms, and stores
- **Zod**: Runtime validation with type inference
- **Radix UI**: Component prop typing
- **Tailwind CSS**: Type-safe className composition
## Examples
All examples are self-contained and demonstrate practical patterns:
- Based on real-world usage
- Follow project best practices
- Include comprehensive comments
- Can be run with `ts-node`
- Ready to adapt to your needs
## Configuration
The skill includes guidance on TypeScript configuration with recommended settings for:
- Strict type checking
- Module resolution
- JSX support
- Path aliases
- Declaration files
## Contributing
When adding new patterns or examples:
1. Follow existing file structure
2. Include comprehensive comments
3. Demonstrate real-world usage
4. Add to appropriate reference file
5. Update this README if needed
## Resources
- [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/)
- [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
- [Type Challenges](https://github.com/type-challenges/type-challenges)
- [TSConfig Reference](https://www.typescriptlang.org/tsconfig)

View File

@@ -0,0 +1,359 @@
---
name: typescript
description: This skill should be used when working with TypeScript code, including type definitions, type inference, generics, utility types, and TypeScript configuration. Provides comprehensive knowledge of TypeScript patterns, best practices, and advanced type system features.
---
# TypeScript Skill
This skill provides comprehensive knowledge and patterns for working with TypeScript effectively in modern applications.
## When to Use This Skill
Use this skill when:
- Writing or refactoring TypeScript code
- Designing type-safe APIs and interfaces
- Working with advanced type system features (generics, conditional types, mapped types)
- Configuring TypeScript projects (tsconfig.json)
- Troubleshooting type errors
- Implementing type-safe patterns with libraries (React, TanStack, etc.)
- Converting JavaScript code to TypeScript
## Core Concepts
### Type System Fundamentals
TypeScript provides static typing for JavaScript with a powerful type system that includes:
- Primitive types (string, number, boolean, null, undefined, symbol, bigint)
- Object types (interfaces, type aliases, classes)
- Array and tuple types
- Union and intersection types
- Literal types and template literal types
- Type inference and type narrowing
- Generic types with constraints
- Conditional types and mapped types
### Type Inference
Leverage TypeScript's type inference to write less verbose code:
- Let TypeScript infer return types when obvious
- Use type inference for variable declarations
- Rely on generic type inference in function calls
- Use `as const` for immutable literal types
### Type Safety Patterns
Implement type-safe patterns:
- Use discriminated unions for state management
- Implement type guards for runtime type checking
- Use branded types for nominal typing
- Leverage conditional types for API design
- Use template literal types for string manipulation
## Key Workflows
### 1. Designing Type-Safe APIs
When designing APIs, follow these patterns:
**Interface vs Type Alias:**
- Use `interface` for object shapes that may be extended
- Use `type` for unions, intersections, and complex type operations
- Use `type` with mapped types and conditional types
**Generic Constraints:**
```typescript
// Use extends for generic constraints
function getValue<T extends { id: string }>(item: T): string {
return item.id
}
```
**Discriminated Unions:**
```typescript
// Use for type-safe state machines
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Data }
| { status: 'error'; error: Error }
```
### 2. Working with Utility Types
Use built-in utility types for common transformations:
- `Partial<T>` - Make all properties optional
- `Required<T>` - Make all properties required
- `Readonly<T>` - Make all properties readonly
- `Pick<T, K>` - Select specific properties
- `Omit<T, K>` - Exclude specific properties
- `Record<K, T>` - Create object type with specific keys
- `Exclude<T, U>` - Exclude types from union
- `Extract<T, U>` - Extract types from union
- `NonNullable<T>` - Remove null/undefined
- `ReturnType<T>` - Get function return type
- `Parameters<T>` - Get function parameter types
- `Awaited<T>` - Unwrap Promise type
### 3. Advanced Type Patterns
**Mapped Types:**
```typescript
// Transform object types
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
type ReadonlyDeep<T> = {
readonly [K in keyof T]: T[K] extends object
? ReadonlyDeep<T[K]>
: T[K]
}
```
**Conditional Types:**
```typescript
// Type-level logic
type IsArray<T> = T extends Array<any> ? true : false
type Flatten<T> = T extends Array<infer U> ? U : T
```
**Template Literal Types:**
```typescript
// String manipulation at type level
type EventName<T extends string> = `on${Capitalize<T>}`
type Route = `/api/${'users' | 'posts'}/${string}`
```
### 4. Type Narrowing
Use type guards and narrowing techniques:
**typeof guards:**
```typescript
if (typeof value === 'string') {
// value is string here
}
```
**instanceof guards:**
```typescript
if (error instanceof Error) {
// error is Error here
}
```
**Custom type guards:**
```typescript
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null && 'id' in value
}
```
**Discriminated unions:**
```typescript
function handle(state: State) {
switch (state.status) {
case 'idle':
// state is { status: 'idle' }
break
case 'success':
// state is { status: 'success'; data: Data }
console.log(state.data)
break
}
}
```
### 5. Working with External Libraries
**Typing Third-Party Libraries:**
- Install type definitions: `npm install --save-dev @types/package-name`
- Create custom declarations in `.d.ts` files when types unavailable
- Use module augmentation to extend existing type definitions
**Declaration Files:**
```typescript
// globals.d.ts
declare global {
interface Window {
myCustomProperty: string
}
}
export {}
```
### 6. TypeScript Configuration
Configure `tsconfig.json` for strict type checking:
**Essential Strict Options:**
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true
}
}
```
## Best Practices
### 1. Prefer Type Inference Over Explicit Types
Let TypeScript infer types when they're obvious from context.
### 2. Use Strict Mode
Enable strict type checking to catch more errors at compile time.
### 3. Avoid `any` Type
Use `unknown` for truly unknown types, then narrow with type guards.
### 4. Use Const Assertions
Use `as const` for immutable values and narrow literal types.
### 5. Leverage Discriminated Unions
Use for state machines and variant types for better type safety.
### 6. Create Reusable Generic Types
Extract common type patterns into reusable generics.
### 7. Use Branded Types for Nominal Typing
Create distinct types for values with same structure but different meaning.
### 8. Document Complex Types
Add JSDoc comments to explain non-obvious type decisions.
### 9. Use Type-Only Imports
Use `import type` for type-only imports to aid tree-shaking.
### 10. Handle Errors with Type Guards
Use type guards to safely work with error objects.
## Common Patterns
### React Component Props
```typescript
// Use interface for component props
interface ButtonProps {
variant?: 'primary' | 'secondary'
size?: 'sm' | 'md' | 'lg'
onClick?: () => void
children: React.ReactNode
}
export function Button({ variant = 'primary', size = 'md', onClick, children }: ButtonProps) {
// implementation
}
```
### API Response Types
```typescript
// Use discriminated unions for API responses
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string }
// Helper for safe API calls
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
try {
const response = await fetch(url)
const data = await response.json()
return { success: true, data }
} catch (error) {
return { success: false, error: String(error) }
}
}
```
### Store/State Types
```typescript
// Use interfaces for state objects
interface AppState {
user: User | null
isAuthenticated: boolean
theme: 'light' | 'dark'
}
// Use type for actions (discriminated union)
type AppAction =
| { type: 'LOGIN'; payload: User }
| { type: 'LOGOUT' }
| { type: 'SET_THEME'; payload: 'light' | 'dark' }
```
## References
For detailed information on specific topics, refer to:
- `references/type-system.md` - Deep dive into TypeScript's type system
- `references/utility-types.md` - Complete guide to built-in utility types
- `references/advanced-types.md` - Advanced type patterns and techniques
- `references/tsconfig-reference.md` - Comprehensive tsconfig.json reference
- `references/common-patterns.md` - Common TypeScript patterns and idioms
- `examples/` - Practical code examples
## Troubleshooting
### Common Type Errors
**Type 'X' is not assignable to type 'Y':**
- Check if types are compatible
- Use type assertions when you know better than the compiler
- Consider using union types or widening the target type
**Object is possibly 'null' or 'undefined':**
- Use optional chaining: `object?.property`
- Use nullish coalescing: `value ?? defaultValue`
- Add type guards or null checks
**Type 'any' implicitly has...**
- Enable strict mode and fix type definitions
- Add explicit type annotations
- Use `unknown` instead of `any` when appropriate
**Cannot find module or its type declarations:**
- Install type definitions: `@types/package-name`
- Create custom `.d.ts` declaration file
- Add to `types` array in tsconfig.json
## Integration with Project Stack
### React 19
Use TypeScript with React 19 features:
- Type component props with interfaces
- Use generic types for hooks
- Type context providers properly
- Use `React.FC` sparingly (prefer explicit typing)
### TanStack Ecosystem
Type TanStack libraries properly:
- TanStack Query: Type query keys and data
- TanStack Router: Use typed route definitions
- TanStack Form: Type form values and validation
- TanStack Store: Type state and actions
### Zod Integration
Combine Zod with TypeScript:
- Use `z.infer<typeof schema>` to extract types from schemas
- Let Zod handle runtime validation
- Use TypeScript for compile-time type checking
## Resources
The TypeScript documentation provides comprehensive information:
- Handbook: https://www.typescriptlang.org/docs/handbook/
- Type manipulation: https://www.typescriptlang.org/docs/handbook/2/types-from-types.html
- Utility types: https://www.typescriptlang.org/docs/handbook/utility-types.html
- TSConfig reference: https://www.typescriptlang.org/tsconfig

View File

@@ -0,0 +1,45 @@
# TypeScript Examples
This directory contains practical TypeScript examples demonstrating various patterns and features.
## Examples
1. **type-system-basics.ts** - Fundamental TypeScript types and features
2. **advanced-types.ts** - Generics, conditional types, and mapped types
3. **react-patterns.ts** - Type-safe React components and hooks
4. **api-patterns.ts** - API response handling with type safety
5. **validation.ts** - Runtime validation with Zod and TypeScript
## How to Use
Each example file is self-contained and demonstrates specific TypeScript concepts. They're based on real-world patterns used in the Plebeian Market application and follow best practices for:
- Type safety
- Error handling
- Code organization
- Reusability
- Maintainability
## Running Examples
These examples are TypeScript files that can be:
- Copied into your project
- Used as reference for patterns
- Modified for your specific needs
- Run with `ts-node` for testing
```bash
# Run an example
npx ts-node examples/type-system-basics.ts
```
## Learning Path
1. Start with `type-system-basics.ts` to understand fundamentals
2. Move to `advanced-types.ts` for complex type patterns
3. Explore `react-patterns.ts` for component typing
4. Study `api-patterns.ts` for type-safe API handling
5. Review `validation.ts` for runtime safety
Each example builds on previous concepts, so following this order is recommended for learners.

View File

@@ -0,0 +1,478 @@
/**
* Advanced TypeScript Types
*
* This file demonstrates advanced TypeScript features including:
* - Generics with constraints
* - Conditional types
* - Mapped types
* - Template literal types
* - Recursive types
* - Utility type implementations
*/
// ============================================================================
// Generics Basics
// ============================================================================
// Generic function
function identity<T>(value: T): T {
return value
}
const stringValue = identity('hello') // Type: string
const numberValue = identity(42) // Type: number
// Generic interface
interface Box<T> {
value: T
}
const stringBox: Box<string> = { value: 'hello' }
const numberBox: Box<number> = { value: 42 }
// Generic class
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
peek(): T | undefined {
return this.items[this.items.length - 1]
}
isEmpty(): boolean {
return this.items.length === 0
}
}
const numberStack = new Stack<number>()
numberStack.push(1)
numberStack.push(2)
numberStack.pop() // Type: number | undefined
// ============================================================================
// Generic Constraints
// ============================================================================
// Constrain to specific type
interface HasLength {
length: number
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length)
}
logLength('string') // OK
logLength([1, 2, 3]) // OK
logLength({ length: 10 }) // OK
// logLength(42) // Error: number doesn't have length
// Constrain to object keys
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
interface User {
id: string
name: string
age: number
}
const user: User = { id: '1', name: 'Alice', age: 30 }
const userName = getProperty(user, 'name') // Type: string
// const invalid = getProperty(user, 'invalid') // Error
// Multiple type parameters with constraints
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 }
}
const merged = merge({ a: 1 }, { b: 2 }) // Type: { a: number } & { b: number }
// ============================================================================
// Conditional Types
// ============================================================================
// Basic conditional type
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
// Nested conditional types
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object'
type T1 = TypeName<string> // "string"
type T2 = TypeName<number> // "number"
type T3 = TypeName<() => void> // "function"
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never
type StrArrOrNumArr = ToArray<string | number> // string[] | number[]
// infer keyword
type Flatten<T> = T extends Array<infer U> ? U : T
type Str = Flatten<string[]> // string
type Num = Flatten<number> // number
// Return type extraction
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
function exampleFn(): string {
return 'hello'
}
type ExampleReturn = MyReturnType<typeof exampleFn> // string
// Parameters extraction
type MyParameters<T> = T extends (...args: infer P) => any ? P : never
function createUser(name: string, age: number): User {
return { id: '1', name, age }
}
type CreateUserParams = MyParameters<typeof createUser> // [string, number]
// ============================================================================
// Mapped Types
// ============================================================================
// Make all properties optional
type MyPartial<T> = {
[K in keyof T]?: T[K]
}
interface Person {
name: string
age: number
email: string
}
type PartialPerson = MyPartial<Person>
// {
// name?: string
// age?: number
// email?: string
// }
// Make all properties required
type MyRequired<T> = {
[K in keyof T]-?: T[K]
}
// Make all properties readonly
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
// Pick specific properties
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
type UserProfile = MyPick<User, 'id' | 'name'>
// { id: string; name: string }
// Omit specific properties
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
type UserWithoutAge = MyOmit<User, 'age'>
// { id: string; name: string }
// Transform property types
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
type NullablePerson = Nullable<Person>
// {
// name: string | null
// age: number | null
// email: string | null
// }
// ============================================================================
// Key Remapping
// ============================================================================
// Add prefix to keys
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
type PersonGetters = Getters<Person>
// {
// getName: () => string
// getAge: () => number
// getEmail: () => string
// }
// Filter keys by type
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
interface Model {
id: number
name: string
description: string
price: number
}
type StringFields = PickByType<Model, string>
// { name: string; description: string }
// Remove specific key
type RemoveKindField<T> = {
[K in keyof T as Exclude<K, 'kind'>]: T[K]
}
// ============================================================================
// Template Literal Types
// ============================================================================
// Event name generation
type EventName<T extends string> = `on${Capitalize<T>}`
type ClickEvent = EventName<'click'> // "onClick"
type SubmitEvent = EventName<'submit'> // "onSubmit"
// Combining literals
type Color = 'red' | 'green' | 'blue'
type Shade = 'light' | 'dark'
type ColorShade = `${Shade}-${Color}`
// "light-red" | "light-green" | "light-blue" | "dark-red" | "dark-green" | "dark-blue"
// CSS properties
type CSSProperty = 'margin' | 'padding'
type Side = 'top' | 'right' | 'bottom' | 'left'
type CSSPropertyWithSide = `${CSSProperty}-${Side}`
// "margin-top" | "margin-right" | ... | "padding-left"
// Route generation
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type Endpoint = '/users' | '/products' | '/orders'
type ApiRoute = `${HttpMethod} ${Endpoint}`
// "GET /users" | "POST /users" | ... | "DELETE /orders"
// ============================================================================
// Recursive Types
// ============================================================================
// JSON value type
type JSONValue = string | number | boolean | null | JSONObject | JSONArray
interface JSONObject {
[key: string]: JSONValue
}
interface JSONArray extends Array<JSONValue> {}
// Tree structure
interface TreeNode<T> {
value: T
children?: TreeNode<T>[]
}
const tree: TreeNode<number> = {
value: 1,
children: [
{ value: 2, children: [{ value: 4 }, { value: 5 }] },
{ value: 3, children: [{ value: 6 }] },
],
}
// Deep readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
interface NestedConfig {
api: {
url: string
timeout: number
}
features: {
darkMode: boolean
}
}
type ImmutableConfig = DeepReadonly<NestedConfig>
// All properties at all levels are readonly
// Deep partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
// ============================================================================
// Advanced Utility Types
// ============================================================================
// Exclude types from union
type MyExclude<T, U> = T extends U ? never : T
type T4 = MyExclude<'a' | 'b' | 'c', 'a'> // "b" | "c"
// Extract types from union
type MyExtract<T, U> = T extends U ? T : never
type T5 = MyExtract<'a' | 'b' | 'c', 'a' | 'f'> // "a"
// NonNullable
type MyNonNullable<T> = T extends null | undefined ? never : T
type T6 = MyNonNullable<string | null | undefined> // string
// Record
type MyRecord<K extends keyof any, T> = {
[P in K]: T
}
type PageInfo = MyRecord<string, number>
// Awaited
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
type T7 = MyAwaited<Promise<string>> // string
type T8 = MyAwaited<Promise<Promise<number>>> // number
// ============================================================================
// Branded Types
// ============================================================================
type Brand<K, T> = K & { __brand: T }
type USD = Brand<number, 'USD'>
type EUR = Brand<number, 'EUR'>
type UserId = Brand<string, 'UserId'>
type ProductId = Brand<string, 'ProductId'>
function makeUSD(amount: number): USD {
return amount as USD
}
function makeUserId(id: string): UserId {
return id as UserId
}
const usd = makeUSD(100)
const userId = makeUserId('user-123')
// Type-safe operations
function addMoney(a: USD, b: USD): USD {
return (a + b) as USD
}
// Prevents mixing different branded types
// const total = addMoney(usd, eur) // Error
// ============================================================================
// Union to Intersection
// ============================================================================
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never
type Union = { a: string } | { b: number }
type Intersection = UnionToIntersection<Union>
// { a: string } & { b: number }
// ============================================================================
// Advanced Generic Patterns
// ============================================================================
// Constraining multiple related types
function merge<
T extends Record<string, any>,
U extends Record<string, any>,
K extends keyof T & keyof U,
>(obj1: T, obj2: U, conflictKeys: K[]): T & U {
const result = { ...obj1, ...obj2 }
conflictKeys.forEach((key) => {
// Handle conflicts
})
return result as T & U
}
// Builder pattern with fluent API
class QueryBuilder<T, Selected extends keyof T = never> {
private selectFields: Set<keyof T> = new Set()
select<K extends keyof T>(
...fields: K[]
): QueryBuilder<T, Selected | K> {
fields.forEach((field) => this.selectFields.add(field))
return this as any
}
execute(): Pick<T, Selected> {
// Execute query
return {} as Pick<T, Selected>
}
}
// Usage
interface Product {
id: string
name: string
price: number
description: string
}
const result = new QueryBuilder<Product>()
.select('id', 'name')
.select('price')
.execute()
// Type: { id: string; name: string; price: number }
// ============================================================================
// Exports
// ============================================================================
export type {
Box,
HasLength,
IsString,
Flatten,
MyPartial,
MyRequired,
MyReadonly,
Nullable,
DeepReadonly,
DeepPartial,
Brand,
USD,
EUR,
UserId,
ProductId,
JSONValue,
TreeNode,
}
export { Stack, identity, getProperty, merge, makeUSD, makeUserId }

View File

@@ -0,0 +1,555 @@
/**
* TypeScript React Patterns
*
* This file demonstrates type-safe React patterns including:
* - Component props typing
* - Hooks with TypeScript
* - Context with type safety
* - Generic components
* - Event handlers
* - Ref types
*/
import { createContext, useContext, useEffect, useReducer, useRef, useState } from 'react'
import type { ReactNode, InputHTMLAttributes, FormEvent, ChangeEvent } from 'react'
// ============================================================================
// Component Props Patterns
// ============================================================================
// Basic component with props
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'tertiary'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
onClick?: () => void
children: ReactNode
}
export function Button({
variant = 'primary',
size = 'md',
disabled = false,
onClick,
children,
}: ButtonProps) {
return (
<button
className={`btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
)
}
// Props extending HTML attributes
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string
error?: string
helperText?: string
}
export function Input({ label, error, helperText, ...inputProps }: InputProps) {
return (
<div className="input-wrapper">
{label && <label>{label}</label>}
<input className={error ? 'input-error' : ''} {...inputProps} />
{error && <span className="error">{error}</span>}
{helperText && <span className="helper">{helperText}</span>}
</div>
)
}
// Generic component
interface ListProps<T> {
items: T[]
renderItem: (item: T, index: number) => ReactNode
keyExtractor: (item: T, index: number) => string
emptyMessage?: string
}
export function List<T>({
items,
renderItem,
keyExtractor,
emptyMessage = 'No items',
}: ListProps<T>) {
if (items.length === 0) {
return <div>{emptyMessage}</div>
}
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item, index)}>{renderItem(item, index)}</li>
))}
</ul>
)
}
// Component with children render prop
interface ContainerProps {
isLoading: boolean
error: Error | null
children: (props: { retry: () => void }) => ReactNode
}
export function Container({ isLoading, error, children }: ContainerProps) {
const retry = () => {
// Retry logic
}
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <>{children({ retry })}</>
}
// ============================================================================
// Hooks Patterns
// ============================================================================
// useState with explicit type
function useCounter(initialValue: number = 0) {
const [count, setCount] = useState<number>(initialValue)
const increment = () => setCount((c) => c + 1)
const decrement = () => setCount((c) => c - 1)
const reset = () => setCount(initialValue)
return { count, increment, decrement, reset }
}
// useState with union type
type LoadingState = 'idle' | 'loading' | 'success' | 'error'
function useLoadingState() {
const [state, setState] = useState<LoadingState>('idle')
const startLoading = () => setState('loading')
const setSuccess = () => setState('success')
const setError = () => setState('error')
const reset = () => setState('idle')
return { state, startLoading, setSuccess, setError, reset }
}
// Custom hook with options
interface UseFetchOptions<T> {
initialData?: T
onSuccess?: (data: T) => void
onError?: (error: Error) => void
}
interface UseFetchReturn<T> {
data: T | undefined
loading: boolean
error: Error | null
refetch: () => Promise<void>
}
function useFetch<T>(url: string, options?: UseFetchOptions<T>): UseFetchReturn<T> {
const [data, setData] = useState<T | undefined>(options?.initialData)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const fetchData = async () => {
setLoading(true)
setError(null)
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const json = await response.json()
setData(json)
options?.onSuccess?.(json)
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err))
setError(error)
options?.onError?.(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchData()
}, [url])
return { data, loading, error, refetch: fetchData }
}
// useReducer with discriminated unions
interface User {
id: string
name: string
email: string
}
type FetchState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
type FetchAction<T> =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; payload: T }
| { type: 'FETCH_ERROR'; error: Error }
| { type: 'RESET' }
function fetchReducer<T>(state: FetchState<T>, action: FetchAction<T>): FetchState<T> {
switch (action.type) {
case 'FETCH_START':
return { status: 'loading' }
case 'FETCH_SUCCESS':
return { status: 'success', data: action.payload }
case 'FETCH_ERROR':
return { status: 'error', error: action.error }
case 'RESET':
return { status: 'idle' }
}
}
function useFetchWithReducer<T>(url: string) {
const [state, dispatch] = useReducer(fetchReducer<T>, { status: 'idle' })
useEffect(() => {
let isCancelled = false
const fetchData = async () => {
dispatch({ type: 'FETCH_START' })
try {
const response = await fetch(url)
const data = await response.json()
if (!isCancelled) {
dispatch({ type: 'FETCH_SUCCESS', payload: data })
}
} catch (error) {
if (!isCancelled) {
dispatch({
type: 'FETCH_ERROR',
error: error instanceof Error ? error : new Error(String(error)),
})
}
}
}
fetchData()
return () => {
isCancelled = true
}
}, [url])
return state
}
// ============================================================================
// Context Patterns
// ============================================================================
// Type-safe context
interface AuthContextType {
user: User | null
isAuthenticated: boolean
login: (email: string, password: string) => Promise<void>
logout: () => void
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const login = async (email: string, password: string) => {
// Login logic
const userData = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
}).then((r) => r.json())
setUser(userData)
}
const logout = () => {
setUser(null)
}
const value: AuthContextType = {
user,
isAuthenticated: user !== null,
login,
logout,
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
// Custom hook with error handling
export function useAuth(): AuthContextType {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
// ============================================================================
// Event Handler Patterns
// ============================================================================
interface FormData {
name: string
email: string
message: string
}
function ContactForm() {
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
message: '',
})
// Type-safe change handler
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target
setFormData((prev) => ({
...prev,
[name]: value,
}))
}
// Type-safe submit handler
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
console.log('Submitting:', formData)
}
// Specific field handler
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({ ...prev, name: e.target.value }))
}
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
placeholder="Message"
/>
<button type="submit">Submit</button>
</form>
)
}
// ============================================================================
// Ref Patterns
// ============================================================================
function FocusInput() {
// useRef with DOM element
const inputRef = useRef<HTMLInputElement>(null)
const focusInput = () => {
inputRef.current?.focus()
}
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
)
}
function Timer() {
// useRef for mutable value
const countRef = useRef<number>(0)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const startTimer = () => {
intervalRef.current = setInterval(() => {
countRef.current += 1
console.log(countRef.current)
}, 1000)
}
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
intervalRef.current = null
}
}
return (
<div>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
)
}
// ============================================================================
// Generic Component Patterns
// ============================================================================
// Select component with generic options
interface SelectProps<T> {
options: T[]
value: T
onChange: (value: T) => void
getLabel: (option: T) => string
getValue: (option: T) => string
}
export function Select<T>({
options,
value,
onChange,
getLabel,
getValue,
}: SelectProps<T>) {
return (
<select
value={getValue(value)}
onChange={(e) => {
const selectedValue = e.target.value
const option = options.find((opt) => getValue(opt) === selectedValue)
if (option) {
onChange(option)
}
}}
>
{options.map((option) => (
<option key={getValue(option)} value={getValue(option)}>
{getLabel(option)}
</option>
))}
</select>
)
}
// Data table component
interface Column<T> {
key: keyof T
header: string
render?: (value: T[keyof T], row: T) => ReactNode
}
interface TableProps<T> {
data: T[]
columns: Column<T>[]
keyExtractor: (row: T) => string
}
export function Table<T>({ data, columns, keyExtractor }: TableProps<T>) {
return (
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={String(col.key)}>{col.header}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row) => (
<tr key={keyExtractor(row)}>
{columns.map((col) => (
<td key={String(col.key)}>
{col.render ? col.render(row[col.key], row) : String(row[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
)
}
// ============================================================================
// Higher-Order Component Pattern
// ============================================================================
interface WithLoadingProps {
isLoading: boolean
}
function withLoading<P extends object>(
Component: React.ComponentType<P>,
): React.FC<P & WithLoadingProps> {
return ({ isLoading, ...props }: WithLoadingProps & P) => {
if (isLoading) {
return <div>Loading...</div>
}
return <Component {...(props as P)} />
}
}
// Usage
interface UserListProps {
users: User[]
}
const UserList: React.FC<UserListProps> = ({ users }) => (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
const UserListWithLoading = withLoading(UserList)
// ============================================================================
// Exports
// ============================================================================
export {
useCounter,
useLoadingState,
useFetch,
useFetchWithReducer,
ContactForm,
FocusInput,
Timer,
}
export type {
ButtonProps,
InputProps,
ListProps,
UseFetchOptions,
UseFetchReturn,
FetchState,
FetchAction,
AuthContextType,
SelectProps,
Column,
TableProps,
}

View File

@@ -0,0 +1,361 @@
/**
* TypeScript Type System Basics
*
* This file demonstrates fundamental TypeScript concepts including:
* - Primitive types
* - Object types (interfaces, type aliases)
* - Union and intersection types
* - Type inference and narrowing
* - Function types
*/
// ============================================================================
// Primitive Types
// ============================================================================
const message: string = 'Hello, TypeScript!'
const count: number = 42
const isActive: boolean = true
const nothing: null = null
const notDefined: undefined = undefined
// ============================================================================
// Object Types
// ============================================================================
// Interface definition
interface User {
id: string
name: string
email: string
age?: number // Optional property
readonly createdAt: Date // Readonly property
}
// Type alias definition
type Product = {
id: string
name: string
price: number
category: string
}
// Creating objects
const user: User = {
id: '1',
name: 'Alice',
email: 'alice@example.com',
createdAt: new Date(),
}
const product: Product = {
id: 'p1',
name: 'Laptop',
price: 999,
category: 'electronics',
}
// ============================================================================
// Union Types
// ============================================================================
type Status = 'idle' | 'loading' | 'success' | 'error'
type ID = string | number
function formatId(id: ID): string {
if (typeof id === 'string') {
return id.toUpperCase()
}
return id.toString()
}
// Discriminated unions
type ApiResponse =
| { success: true; data: User }
| { success: false; error: string }
function handleResponse(response: ApiResponse) {
if (response.success) {
// TypeScript knows response.data exists here
console.log(response.data.name)
} else {
// TypeScript knows response.error exists here
console.error(response.error)
}
}
// ============================================================================
// Intersection Types
// ============================================================================
type Timestamped = {
createdAt: Date
updatedAt: Date
}
type TimestampedUser = User & Timestamped
const timestampedUser: TimestampedUser = {
id: '1',
name: 'Bob',
email: 'bob@example.com',
createdAt: new Date(),
updatedAt: new Date(),
}
// ============================================================================
// Array Types
// ============================================================================
const numbers: number[] = [1, 2, 3, 4, 5]
const strings: Array<string> = ['a', 'b', 'c']
const users: User[] = [user, timestampedUser]
// Readonly arrays
const immutableNumbers: readonly number[] = [1, 2, 3]
// immutableNumbers.push(4) // Error: push does not exist on readonly array
// ============================================================================
// Tuple Types
// ============================================================================
type Point = [number, number]
type NamedPoint = [x: number, y: number, z?: number]
const point: Point = [10, 20]
const namedPoint: NamedPoint = [10, 20, 30]
// ============================================================================
// Function Types
// ============================================================================
// Function declaration
function add(a: number, b: number): number {
return a + b
}
// Arrow function
const subtract = (a: number, b: number): number => a - b
// Function type alias
type MathOperation = (a: number, b: number) => number
const multiply: MathOperation = (a, b) => a * b
// Optional parameters
function greet(name: string, greeting?: string): string {
return `${greeting ?? 'Hello'}, ${name}!`
}
// Default parameters
function createUser(name: string, role: string = 'user'): User {
return {
id: Math.random().toString(),
name,
email: `${name.toLowerCase()}@example.com`,
createdAt: new Date(),
}
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0)
}
// ============================================================================
// Type Inference
// ============================================================================
// Type is inferred as string
let inferredString = 'hello'
// Type is inferred as number
let inferredNumber = 42
// Type is inferred as { name: string; age: number }
let inferredObject = {
name: 'Alice',
age: 30,
}
// Return type is inferred as number
function inferredReturn(a: number, b: number) {
return a + b
}
// ============================================================================
// Type Narrowing
// ============================================================================
// typeof guard
function processValue(value: string | number) {
if (typeof value === 'string') {
// value is string here
return value.toUpperCase()
}
// value is number here
return value.toFixed(2)
}
// Truthiness narrowing
function printName(name: string | null | undefined) {
if (name) {
// name is string here
console.log(name.toUpperCase())
}
}
// Equality narrowing
function example(x: string | number, y: string | boolean) {
if (x === y) {
// x and y are both string here
console.log(x.toUpperCase(), y.toLowerCase())
}
}
// in operator narrowing
type Fish = { swim: () => void }
type Bird = { fly: () => void }
function move(animal: Fish | Bird) {
if ('swim' in animal) {
// animal is Fish here
animal.swim()
} else {
// animal is Bird here
animal.fly()
}
}
// instanceof narrowing
function processError(error: Error | string) {
if (error instanceof Error) {
// error is Error here
console.error(error.message)
} else {
// error is string here
console.error(error)
}
}
// ============================================================================
// Type Predicates (Custom Type Guards)
// ============================================================================
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
'email' in value
)
}
function processData(data: unknown) {
if (isUser(data)) {
// data is User here
console.log(data.name)
}
}
// ============================================================================
// Const Assertions
// ============================================================================
// Without const assertion
const mutableConfig = {
host: 'localhost',
port: 8080,
}
// mutableConfig.host = 'example.com' // OK
// With const assertion
const immutableConfig = {
host: 'localhost',
port: 8080,
} as const
// immutableConfig.host = 'example.com' // Error: cannot assign to readonly property
// Array with const assertion
const directions = ['north', 'south', 'east', 'west'] as const
// Type: readonly ["north", "south", "east", "west"]
// ============================================================================
// Literal Types
// ============================================================================
type Direction = 'north' | 'south' | 'east' | 'west'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6
function move(direction: Direction, steps: number) {
console.log(`Moving ${direction} by ${steps} steps`)
}
move('north', 10) // OK
// move('up', 10) // Error: "up" is not assignable to Direction
// ============================================================================
// Index Signatures
// ============================================================================
interface StringMap {
[key: string]: string
}
const translations: StringMap = {
hello: 'Hola',
goodbye: 'Adiós',
thanks: 'Gracias',
}
// ============================================================================
// Utility Functions
// ============================================================================
// Type-safe object keys
function getObjectKeys<T extends object>(obj: T): Array<keyof T> {
return Object.keys(obj) as Array<keyof T>
}
// Type-safe property access
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const userName = getProperty(user, 'name') // Type: string
const userAge = getProperty(user, 'age') // Type: number | undefined
// ============================================================================
// Named Return Values (Go-style)
// ============================================================================
function parseJSON(json: string): { data: unknown | null; err: Error | null } {
let data: unknown | null = null
let err: Error | null = null
try {
data = JSON.parse(json)
} catch (error) {
err = error instanceof Error ? error : new Error(String(error))
}
return { data, err }
}
// Usage
const { data, err } = parseJSON('{"name": "Alice"}')
if (err) {
console.error('Failed to parse JSON:', err.message)
} else {
console.log('Parsed data:', data)
}
// ============================================================================
// Exports
// ============================================================================
export type { User, Product, Status, ID, ApiResponse, TimestampedUser }
export { formatId, handleResponse, processValue, isUser, getProperty, parseJSON }

View File

@@ -0,0 +1,395 @@
# TypeScript Quick Reference
Quick lookup guide for common TypeScript patterns and syntax.
## Basic Types
```typescript
// Primitives
string, number, boolean, null, undefined, symbol, bigint
// Special types
any // Avoid - disables type checking
unknown // Type-safe alternative to any
void // No return value
never // Never returns
// Arrays
number[]
Array<string>
readonly number[]
// Tuples
[string, number]
[x: number, y: number]
// Objects
{ name: string; age: number }
Record<string, number>
```
## Type Declarations
```typescript
// Interface
interface User {
id: string
name: string
age?: number // Optional
readonly createdAt: Date // Readonly
}
// Type alias
type Status = 'idle' | 'loading' | 'success' | 'error'
type ID = string | number
type Point = { x: number; y: number }
// Function type
type Callback = (data: string) => void
type MathOp = (a: number, b: number) => number
```
## Union & Intersection
```typescript
// Union (OR)
string | number
type Result = Success | Error
// Intersection (AND)
A & B
type Combined = User & Timestamped
// Discriminated union
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Data }
| { status: 'error'; error: Error }
```
## Generics
```typescript
// Generic function
function identity<T>(value: T): T
// Generic interface
interface Box<T> { value: T }
// Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]
// Multiple type parameters
function merge<T, U>(a: T, b: U): T & U
// Default type parameter
interface Response<T = unknown> { data: T }
```
## Utility Types
```typescript
Partial<T> // Make all optional
Required<T> // Make all required
Readonly<T> // Make all readonly
Pick<T, K> // Select properties
Omit<T, K> // Exclude properties
Record<K, T> // Object with specific keys
Exclude<T, U> // Remove from union
Extract<T, U> // Extract from union
NonNullable<T> // Remove null/undefined
ReturnType<T> // Get function return type
Parameters<T> // Get function parameters
Awaited<T> // Unwrap Promise
```
## Type Guards
```typescript
// typeof
if (typeof value === 'string') { }
// instanceof
if (error instanceof Error) { }
// in operator
if ('property' in object) { }
// Custom type guard
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null && 'id' in value
}
// Assertion function
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') throw new Error()
}
```
## Advanced Types
```typescript
// Conditional types
type IsString<T> = T extends string ? true : false
// Mapped types
type Nullable<T> = { [K in keyof T]: T[K] | null }
// Template literal types
type EventName<T extends string> = `on${Capitalize<T>}`
// Key remapping
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
// infer keyword
type Flatten<T> = T extends Array<infer U> ? U : T
```
## Functions
```typescript
// Function declaration
function add(a: number, b: number): number { return a + b }
// Arrow function
const subtract = (a: number, b: number): number => a - b
// Optional parameters
function greet(name: string, greeting?: string): string { }
// Default parameters
function create(name: string, role = 'user'): User { }
// Rest parameters
function sum(...numbers: number[]): number { }
// Overloads
function format(value: string): string
function format(value: number): string
function format(value: string | number): string { }
```
## Classes
```typescript
class User {
// Properties
private id: string
public name: string
protected age: number
readonly createdAt: Date
// Constructor
constructor(name: string) {
this.name = name
this.createdAt = new Date()
}
// Methods
greet(): string {
return `Hello, ${this.name}`
}
// Static
static create(name: string): User {
return new User(name)
}
// Getters/Setters
get displayName(): string {
return this.name.toUpperCase()
}
}
// Inheritance
class Admin extends User {
constructor(name: string, public permissions: string[]) {
super(name)
}
}
// Abstract class
abstract class Animal {
abstract makeSound(): void
}
```
## React Patterns
```typescript
// Component props
interface ButtonProps {
variant?: 'primary' | 'secondary'
onClick?: () => void
children: React.ReactNode
}
export function Button({ variant = 'primary', onClick, children }: ButtonProps) { }
// Generic component
interface ListProps<T> {
items: T[]
renderItem: (item: T) => React.ReactNode
}
export function List<T>({ items, renderItem }: ListProps<T>) { }
// Hooks
const [state, setState] = useState<string>('')
const [data, setData] = useState<User | null>(null)
// Context
interface AuthContextType {
user: User | null
login: () => Promise<void>
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function useAuth(): AuthContextType {
const context = useContext(AuthContext)
if (!context) throw new Error('useAuth must be used within AuthProvider')
return context
}
```
## Common Patterns
### Result Type
```typescript
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E }
```
### Option Type
```typescript
type Option<T> = Some<T> | None
interface Some<T> { _tag: 'Some'; value: T }
interface None { _tag: 'None' }
```
### Branded Types
```typescript
type Brand<K, T> = K & { __brand: T }
type UserId = Brand<string, 'UserId'>
```
### Named Returns (Go-style)
```typescript
function parseJSON(json: string): { data: unknown | null; err: Error | null } {
let data: unknown | null = null
let err: Error | null = null
try {
data = JSON.parse(json)
} catch (error) {
err = error instanceof Error ? error : new Error(String(error))
}
return { data, err }
}
```
## Type Assertions
```typescript
// as syntax (preferred)
const value = input as string
// Angle bracket syntax (not in JSX)
const value = <string>input
// as const
const config = { host: 'localhost' } as const
// Non-null assertion (use sparingly)
const element = document.getElementById('app')!
```
## Type Narrowing
```typescript
// Control flow
if (value !== null) {
// value is non-null here
}
// Switch with discriminated unions
switch (state.status) {
case 'success':
console.log(state.data) // TypeScript knows data exists
break
case 'error':
console.log(state.error) // TypeScript knows error exists
break
}
// Optional chaining
user?.profile?.name
// Nullish coalescing
const name = user?.name ?? 'Anonymous'
```
## Module Syntax
```typescript
// Named exports
export function helper() { }
export const CONFIG = { }
// Default export
export default class App { }
// Type-only imports/exports
import type { User } from './types'
export type { User }
// Namespace imports
import * as utils from './utils'
```
## TSConfig Essentials
```json
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}
```
## Common Errors & Fixes
| Error | Fix |
|-------|-----|
| Type 'X' is not assignable to type 'Y' | Check type compatibility, use type assertion if needed |
| Object is possibly 'null' | Use optional chaining `?.` or null check |
| Cannot find module | Install `@types/package-name` |
| Implicit any | Add type annotation or enable strict mode |
| Property does not exist | Check object shape, use type guard |
## Best Practices
1. Enable `strict` mode in tsconfig.json
2. Avoid `any`, use `unknown` instead
3. Use discriminated unions for state
4. Leverage type inference
5. Use `const` assertions for immutable data
6. Create custom type guards for runtime safety
7. Use utility types instead of recreating
8. Document complex types with JSDoc
9. Prefer interfaces for objects, types for unions
10. Use branded types for domain-specific primitives

View File

@@ -0,0 +1,756 @@
# TypeScript Common Patterns Reference
This document contains commonly used TypeScript patterns and idioms from real-world applications.
## React Patterns
### Component Props
```typescript
// Basic props with children
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'tertiary'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
onClick?: () => void
children: React.ReactNode
}
export function Button({
variant = 'primary',
size = 'md',
disabled = false,
onClick,
children,
}: ButtonProps) {
return (
<button className={`btn-${variant} btn-${size}`} disabled={disabled} onClick={onClick}>
{children}
</button>
)
}
// Props extending HTML attributes
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string
error?: string
}
export function Input({ label, error, ...inputProps }: InputProps) {
return (
<div>
{label && <label>{label}</label>}
<input {...inputProps} />
{error && <span>{error}</span>}
</div>
)
}
// Generic component props
interface ListProps<T> {
items: T[]
renderItem: (item: T) => React.ReactNode
keyExtractor: (item: T) => string
}
export function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
)
}
```
### Hooks
```typescript
// Custom hook with return type
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
return initialValue
}
})
const setValue = (value: T) => {
setStoredValue(value)
window.localStorage.setItem(key, JSON.stringify(value))
}
return [storedValue, setValue]
}
// Hook with options object
interface UseFetchOptions<T> {
initialData?: T
onSuccess?: (data: T) => void
onError?: (error: Error) => void
}
function useFetch<T>(url: string, options?: UseFetchOptions<T>) {
const [data, setData] = useState<T | undefined>(options?.initialData)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
let isCancelled = false
const fetchData = async () => {
setLoading(true)
try {
const response = await fetch(url)
const json = await response.json()
if (!isCancelled) {
setData(json)
options?.onSuccess?.(json)
}
} catch (err) {
if (!isCancelled) {
const error = err instanceof Error ? err : new Error(String(err))
setError(error)
options?.onError?.(error)
}
} finally {
if (!isCancelled) {
setLoading(false)
}
}
}
fetchData()
return () => {
isCancelled = true
}
}, [url])
return { data, loading, error }
}
```
### Context
```typescript
// Type-safe context
interface AuthContextType {
user: User | null
login: (email: string, password: string) => Promise<void>
logout: () => void
isAuthenticated: boolean
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const login = async (email: string, password: string) => {
// Login logic
const user = await api.login(email, password)
setUser(user)
}
const logout = () => {
setUser(null)
}
const value: AuthContextType = {
user,
login,
logout,
isAuthenticated: user !== null,
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
// Custom hook with proper error handling
export function useAuth(): AuthContextType {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
```
## API Response Patterns
### Result Type Pattern
```typescript
// Discriminated union for API responses
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E }
// Helper functions
function success<T>(data: T): Result<T> {
return { success: true, data }
}
function failure<E = Error>(error: E): Result<never, E> {
return { success: false, error }
}
// Usage
async function fetchUser(id: string): Promise<Result<User>> {
try {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
return failure(new Error(`HTTP ${response.status}`))
}
const data = await response.json()
return success(data)
} catch (error) {
return failure(error instanceof Error ? error : new Error(String(error)))
}
}
// Consuming the result
const result = await fetchUser('123')
if (result.success) {
console.log(result.data.name) // Type-safe access
} else {
console.error(result.error.message) // Type-safe error handling
}
```
### Option Type Pattern
```typescript
// Option/Maybe type for nullable values
type Option<T> = Some<T> | None
interface Some<T> {
readonly _tag: 'Some'
readonly value: T
}
interface None {
readonly _tag: 'None'
}
// Constructors
function some<T>(value: T): Option<T> {
return { _tag: 'Some', value }
}
function none(): Option<never> {
return { _tag: 'None' }
}
// Helper functions
function isSome<T>(option: Option<T>): option is Some<T> {
return option._tag === 'Some'
}
function isNone<T>(option: Option<T>): option is None {
return option._tag === 'None'
}
function map<T, U>(option: Option<T>, fn: (value: T) => U): Option<U> {
return isSome(option) ? some(fn(option.value)) : none()
}
function getOrElse<T>(option: Option<T>, defaultValue: T): T {
return isSome(option) ? option.value : defaultValue
}
// Usage
function findUser(id: string): Option<User> {
const user = users.find((u) => u.id === id)
return user ? some(user) : none()
}
const user = findUser('123')
const userName = getOrElse(map(user, (u) => u.name), 'Unknown')
```
## State Management Patterns
### Discriminated Union for State
```typescript
// State machine using discriminated unions
type FetchState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
// Reducer pattern
type FetchAction<T> =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; payload: T }
| { type: 'FETCH_ERROR'; error: Error }
| { type: 'RESET' }
function fetchReducer<T>(state: FetchState<T>, action: FetchAction<T>): FetchState<T> {
switch (action.type) {
case 'FETCH_START':
return { status: 'loading' }
case 'FETCH_SUCCESS':
return { status: 'success', data: action.payload }
case 'FETCH_ERROR':
return { status: 'error', error: action.error }
case 'RESET':
return { status: 'idle' }
}
}
// Usage in component
function UserProfile({ userId }: { userId: string }) {
const [state, dispatch] = useReducer(fetchReducer<User>, { status: 'idle' })
useEffect(() => {
dispatch({ type: 'FETCH_START' })
fetchUser(userId)
.then((user) => dispatch({ type: 'FETCH_SUCCESS', payload: user }))
.catch((error) => dispatch({ type: 'FETCH_ERROR', error }))
}, [userId])
switch (state.status) {
case 'idle':
return <div>Ready to load</div>
case 'loading':
return <div>Loading...</div>
case 'success':
return <div>{state.data.name}</div>
case 'error':
return <div>Error: {state.error.message}</div>
}
}
```
### Store Pattern
```typescript
// Type-safe store implementation
interface Store<T> {
getState: () => T
setState: (partial: Partial<T>) => void
subscribe: (listener: (state: T) => void) => () => void
}
function createStore<T>(initialState: T): Store<T> {
let state = initialState
const listeners = new Set<(state: T) => void>()
return {
getState: () => state,
setState: (partial) => {
state = { ...state, ...partial }
listeners.forEach((listener) => listener(state))
},
subscribe: (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
}
}
// Usage
interface AppState {
user: User | null
theme: 'light' | 'dark'
}
const store = createStore<AppState>({
user: null,
theme: 'light',
})
// React hook integration
function useStore<T, U>(store: Store<T>, selector: (state: T) => U): U {
const [value, setValue] = useState(() => selector(store.getState()))
useEffect(() => {
const unsubscribe = store.subscribe((state) => {
setValue(selector(state))
})
return unsubscribe
}, [store, selector])
return value
}
// Usage in component
function ThemeToggle() {
const theme = useStore(store, (state) => state.theme)
return (
<button
onClick={() => store.setState({ theme: theme === 'light' ? 'dark' : 'light' })}
>
Toggle Theme
</button>
)
}
```
## Form Patterns
### Form State Management
```typescript
// Generic form state
interface FormState<T> {
values: T
errors: Partial<Record<keyof T, string>>
touched: Partial<Record<keyof T, boolean>>
isSubmitting: boolean
}
// Form hook
function useForm<T extends Record<string, any>>(
initialValues: T,
validate: (values: T) => Partial<Record<keyof T, string>>,
) {
const [state, setState] = useState<FormState<T>>({
values: initialValues,
errors: {},
touched: {},
isSubmitting: false,
})
const handleChange = <K extends keyof T>(field: K, value: T[K]) => {
setState((prev) => ({
...prev,
values: { ...prev.values, [field]: value },
errors: { ...prev.errors, [field]: undefined },
}))
}
const handleBlur = <K extends keyof T>(field: K) => {
setState((prev) => ({
...prev,
touched: { ...prev.touched, [field]: true },
}))
}
const handleSubmit = async (onSubmit: (values: T) => Promise<void>) => {
const errors = validate(state.values)
if (Object.keys(errors).length > 0) {
setState((prev) => ({
...prev,
errors,
touched: Object.keys(state.values).reduce(
(acc, key) => ({ ...acc, [key]: true }),
{},
),
}))
return
}
setState((prev) => ({ ...prev, isSubmitting: true }))
try {
await onSubmit(state.values)
} finally {
setState((prev) => ({ ...prev, isSubmitting: false }))
}
}
return {
values: state.values,
errors: state.errors,
touched: state.touched,
isSubmitting: state.isSubmitting,
handleChange,
handleBlur,
handleSubmit,
}
}
// Usage
interface LoginFormValues {
email: string
password: string
}
function LoginForm() {
const form = useForm<LoginFormValues>(
{ email: '', password: '' },
(values) => {
const errors: Partial<Record<keyof LoginFormValues, string>> = {}
if (!values.email) {
errors.email = 'Email is required'
}
if (!values.password) {
errors.password = 'Password is required'
}
return errors
},
)
return (
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit(async (values) => {
await login(values.email, values.password)
})
}}
>
<input
value={form.values.email}
onChange={(e) => form.handleChange('email', e.target.value)}
onBlur={() => form.handleBlur('email')}
/>
{form.touched.email && form.errors.email && <span>{form.errors.email}</span>}
<input
type="password"
value={form.values.password}
onChange={(e) => form.handleChange('password', e.target.value)}
onBlur={() => form.handleBlur('password')}
/>
{form.touched.password && form.errors.password && (
<span>{form.errors.password}</span>
)}
<button type="submit" disabled={form.isSubmitting}>
Login
</button>
</form>
)
}
```
## Validation Patterns
### Zod Integration
```typescript
import { z } from 'zod'
// Schema definition
const userSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(120),
role: z.enum(['admin', 'user', 'guest']),
})
// Extract type from schema
type User = z.infer<typeof userSchema>
// Validation function
function validateUser(data: unknown): Result<User> {
const result = userSchema.safeParse(data)
if (result.success) {
return { success: true, data: result.data }
}
return {
success: false,
error: new Error(result.error.errors.map((e) => e.message).join(', ')),
}
}
// API integration
async function createUser(data: unknown): Promise<Result<User>> {
const validation = validateUser(data)
if (!validation.success) {
return validation
}
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(validation.data),
})
if (!response.ok) {
return failure(new Error(`HTTP ${response.status}`))
}
const user = await response.json()
return success(user)
} catch (error) {
return failure(error instanceof Error ? error : new Error(String(error)))
}
}
```
## Builder Pattern
```typescript
// Fluent builder pattern
class QueryBuilder<T> {
private filters: Array<(item: T) => boolean> = []
private sortFn?: (a: T, b: T) => number
private limitValue?: number
where(predicate: (item: T) => boolean): this {
this.filters.push(predicate)
return this
}
sortBy(compareFn: (a: T, b: T) => number): this {
this.sortFn = compareFn
return this
}
limit(count: number): this {
this.limitValue = count
return this
}
execute(data: T[]): T[] {
let result = data
// Apply filters
this.filters.forEach((filter) => {
result = result.filter(filter)
})
// Apply sorting
if (this.sortFn) {
result = result.sort(this.sortFn)
}
// Apply limit
if (this.limitValue !== undefined) {
result = result.slice(0, this.limitValue)
}
return result
}
}
// Usage
interface Product {
id: string
name: string
price: number
category: string
}
const products: Product[] = [
/* ... */
]
const query = new QueryBuilder<Product>()
.where((p) => p.category === 'electronics')
.where((p) => p.price < 1000)
.sortBy((a, b) => a.price - b.price)
.limit(10)
.execute(products)
```
## Factory Pattern
```typescript
// Abstract factory pattern with TypeScript
interface Button {
render: () => string
onClick: () => void
}
interface ButtonFactory {
createButton: (label: string, onClick: () => void) => Button
}
class PrimaryButton implements Button {
constructor(private label: string, private clickHandler: () => void) {}
render() {
return `<button class="primary">${this.label}</button>`
}
onClick() {
this.clickHandler()
}
}
class SecondaryButton implements Button {
constructor(private label: string, private clickHandler: () => void) {}
render() {
return `<button class="secondary">${this.label}</button>`
}
onClick() {
this.clickHandler()
}
}
class PrimaryButtonFactory implements ButtonFactory {
createButton(label: string, onClick: () => void): Button {
return new PrimaryButton(label, onClick)
}
}
class SecondaryButtonFactory implements ButtonFactory {
createButton(label: string, onClick: () => void): Button {
return new SecondaryButton(label, onClick)
}
}
// Usage
function createUI(factory: ButtonFactory) {
const button = factory.createButton('Click me', () => console.log('Clicked!'))
return button.render()
}
```
## Named Return Variables Pattern
```typescript
// Following Go-style named returns
function parseUser(data: unknown): { user: User | null; err: Error | null } {
let user: User | null = null
let err: Error | null = null
try {
user = userSchema.parse(data)
} catch (error) {
err = error instanceof Error ? error : new Error(String(error))
}
return { user, err }
}
// With explicit naming
function fetchData(url: string): {
data: unknown | null
status: number
err: Error | null
} {
let data: unknown | null = null
let status = 0
let err: Error | null = null
try {
const response = fetch(url)
// Process response
} catch (error) {
err = error instanceof Error ? error : new Error(String(error))
}
return { data, status, err }
}
```
## Best Practices
1. **Use discriminated unions** for type-safe state management
2. **Leverage generic types** for reusable components and hooks
3. **Extract types from Zod schemas** for runtime + compile-time safety
4. **Use Result/Option types** for explicit error handling
5. **Create builder patterns** for complex object construction
6. **Use factory patterns** for flexible object creation
7. **Type context properly** to catch usage errors at compile time
8. **Prefer const assertions** for immutable configurations
9. **Use branded types** for domain-specific primitives
10. **Document patterns** with JSDoc for team knowledge sharing

View File

@@ -0,0 +1,804 @@
# TypeScript Type System Reference
## Overview
TypeScript's type system is structural (duck-typed) rather than nominal. Two types are compatible if their structure matches, regardless of their names.
## Primitive Types
### Basic Primitives
```typescript
let str: string = 'hello'
let num: number = 42
let bool: boolean = true
let nul: null = null
let undef: undefined = undefined
let sym: symbol = Symbol('key')
let big: bigint = 100n
```
### Special Types
**any** - Disables type checking (avoid when possible):
```typescript
let anything: any = 'string'
anything = 42 // OK
anything.nonExistent() // OK at compile time, error at runtime
```
**unknown** - Type-safe alternative to any (requires type checking):
```typescript
let value: unknown = 'string'
// value.toUpperCase() // Error: must narrow type first
if (typeof value === 'string') {
value.toUpperCase() // OK after narrowing
}
```
**void** - Absence of a value (function return type):
```typescript
function log(message: string): void {
console.log(message)
}
```
**never** - Value that never occurs (exhaustive checks, infinite loops):
```typescript
function throwError(message: string): never {
throw new Error(message)
}
function exhaustiveCheck(value: never): never {
throw new Error(`Unhandled case: ${value}`)
}
```
## Object Types
### Interfaces
```typescript
// Basic interface
interface User {
id: string
name: string
email: string
}
// Optional properties
interface Product {
id: string
name: string
description?: string // Optional
}
// Readonly properties
interface Config {
readonly apiUrl: string
readonly timeout: number
}
// Index signatures
interface Dictionary {
[key: string]: string
}
// Method signatures
interface Calculator {
add(a: number, b: number): number
subtract(a: number, b: number): number
}
// Extending interfaces
interface Employee extends User {
role: string
department: string
}
// Multiple inheritance
interface Admin extends User, Employee {
permissions: string[]
}
```
### Type Aliases
```typescript
// Basic type alias
type ID = string | number
// Object type
type Point = {
x: number
y: number
}
// Union type
type Status = 'idle' | 'loading' | 'success' | 'error'
// Intersection type
type Timestamped = {
createdAt: Date
updatedAt: Date
}
type TimestampedUser = User & Timestamped
// Function type
type Callback = (data: string) => void
// Generic type alias
type Result<T> = { success: true; data: T } | { success: false; error: string }
```
### Interface vs Type Alias
**Use interface when:**
- Defining object shapes
- Need declaration merging
- Building public API types that others might extend
**Use type when:**
- Creating unions or intersections
- Working with mapped types
- Need conditional types
- Defining primitive aliases
## Array and Tuple Types
### Arrays
```typescript
// Array syntax
let numbers: number[] = [1, 2, 3]
let strings: Array<string> = ['a', 'b', 'c']
// Readonly arrays
let immutable: readonly number[] = [1, 2, 3]
let alsoImmutable: ReadonlyArray<string> = ['a', 'b']
```
### Tuples
```typescript
// Fixed-length, mixed-type arrays
type Point = [number, number]
type NamedPoint = [x: number, y: number]
// Optional elements
type OptionalTuple = [string, number?]
// Rest elements
type StringNumberBooleans = [string, number, ...boolean[]]
// Readonly tuples
type ReadonlyPair = readonly [string, number]
```
## Union and Intersection Types
### Union Types
```typescript
// Value can be one of several types
type StringOrNumber = string | number
function format(value: StringOrNumber): string {
if (typeof value === 'string') {
return value
}
return value.toString()
}
// Discriminated unions
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; size: number }
| { kind: 'rectangle'; width: number; height: number }
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2
case 'square':
return shape.size ** 2
case 'rectangle':
return shape.width * shape.height
}
}
```
### Intersection Types
```typescript
// Combine multiple types
type Draggable = {
drag: () => void
}
type Resizable = {
resize: () => void
}
type UIWidget = Draggable & Resizable
const widget: UIWidget = {
drag: () => console.log('dragging'),
resize: () => console.log('resizing'),
}
```
## Literal Types
### String Literal Types
```typescript
type Direction = 'north' | 'south' | 'east' | 'west'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
function move(direction: Direction) {
// direction can only be one of the four values
}
```
### Number Literal Types
```typescript
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6
type PowerOfTwo = 1 | 2 | 4 | 8 | 16 | 32
```
### Boolean Literal Types
```typescript
type Yes = true
type No = false
```
### Template Literal Types
```typescript
// String manipulation at type level
type EventName<T extends string> = `on${Capitalize<T>}`
type ClickEvent = EventName<'click'> // "onClick"
// Combining literals
type Color = 'red' | 'blue' | 'green'
type Shade = 'light' | 'dark'
type ColorShade = `${Shade}-${Color}` // "light-red" | "light-blue" | ...
// Extract patterns
type EmailLocaleIDs = 'welcome_email' | 'email_heading'
type FooterLocaleIDs = 'footer_title' | 'footer_sendoff'
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`
```
## Type Inference
### Automatic Inference
```typescript
// Type inferred as string
let message = 'hello'
// Type inferred as number[]
let numbers = [1, 2, 3]
// Type inferred as { name: string; age: number }
let person = {
name: 'Alice',
age: 30,
}
// Return type inferred
function add(a: number, b: number) {
return a + b // Returns number
}
```
### Const Assertions
```typescript
// Without const assertion
let colors1 = ['red', 'green', 'blue'] // Type: string[]
// With const assertion
let colors2 = ['red', 'green', 'blue'] as const // Type: readonly ["red", "green", "blue"]
// Object with const assertion
const config = {
host: 'localhost',
port: 8080,
} as const // All properties become readonly with literal types
```
### Type Inference in Generics
```typescript
// Generic type inference from usage
function identity<T>(value: T): T {
return value
}
let str = identity('hello') // T inferred as string
let num = identity(42) // T inferred as number
// Multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second]
}
let p = pair('hello', 42) // [string, number]
```
## Type Narrowing
### typeof Guards
```typescript
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
// padding is number here
return ' '.repeat(padding) + value
}
// padding is string here
return padding + value
}
```
### instanceof Guards
```typescript
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
```typescript
type Fish = { swim: () => void }
type Bird = { fly: () => void }
function move(animal: Fish | Bird) {
if ('swim' in animal) {
animal.swim()
} else {
animal.fly()
}
}
```
### Equality Narrowing
```typescript
function example(x: string | number, y: string | boolean) {
if (x === y) {
// x and y are both string here
x.toUpperCase()
y.toLowerCase()
}
}
```
### Control Flow Analysis
```typescript
function example(value: string | null) {
if (value === null) {
return
}
// value is string here (null eliminated)
console.log(value.toUpperCase())
}
```
### Type Predicates (Custom Type Guards)
```typescript
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function example(value: unknown) {
if (isString(value)) {
// value is string here
console.log(value.toUpperCase())
}
}
// More complex example
interface User {
id: string
name: string
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
typeof (value as User).id === 'string' &&
typeof (value as User).name === 'string'
)
}
```
### Assertion Functions
```typescript
function assert(condition: unknown, message?: string): asserts condition {
if (!condition) {
throw new Error(message || 'Assertion failed')
}
}
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value must be a string')
}
}
function example(value: unknown) {
assertIsString(value)
// value is string here
console.log(value.toUpperCase())
}
```
## Generic Types
### Basic Generics
```typescript
// Generic function
function first<T>(items: T[]): T | undefined {
return items[0]
}
// Generic interface
interface Box<T> {
value: T
}
// Generic type alias
type Result<T> = { success: true; data: T } | { success: false; error: string }
// Generic class
class Stack<T> {
private items: T[] = []
push(item: T) {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
}
```
### Generic Constraints
```typescript
// Constrain to specific type
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
// Constrain to interface
interface HasLength {
length: number
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length)
}
logLength('string') // OK
logLength([1, 2, 3]) // OK
logLength({ length: 10 }) // OK
// logLength(42) // Error: number doesn't have length
```
### Default Generic Parameters
```typescript
interface Response<T = unknown> {
data: T
status: number
}
// Uses default
let response1: Response = { data: 'anything', status: 200 }
// Explicitly typed
let response2: Response<User> = { data: user, status: 200 }
```
### Generic Utility Functions
```typescript
// Pick specific properties
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>
keys.forEach((key) => {
result[key] = obj[key]
})
return result
}
// Map array
function map<T, U>(items: T[], fn: (item: T) => U): U[] {
return items.map(fn)
}
```
## Advanced Type Features
### Conditional Types
```typescript
// Basic conditional type
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never
type StrArrOrNumArr = ToArray<string | number> // string[] | number[]
// Infer keyword
type Flatten<T> = T extends Array<infer U> ? U : T
type Str = Flatten<string[]> // string
type Num = Flatten<number> // number
// ReturnType implementation
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
```
### Mapped Types
```typescript
// Make all properties optional
type Partial<T> = {
[K in keyof T]?: T[K]
}
// Make all properties required
type Required<T> = {
[K in keyof T]-?: T[K]
}
// Make all properties readonly
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
// Transform keys
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
// }
```
### Key Remapping
```typescript
// Filter keys
type RemoveKindField<T> = {
[K in keyof T as Exclude<K, 'kind'>]: T[K]
}
// Conditional key inclusion
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
interface Model {
id: number
name: string
age: number
email: string
}
type StringFields = PickByType<Model, string> // { name: string, email: string }
```
### Recursive Types
```typescript
// JSON value type
type JSONValue = string | number | boolean | null | JSONObject | JSONArray
interface JSONObject {
[key: string]: JSONValue
}
interface JSONArray extends Array<JSONValue> {}
// Tree structure
interface TreeNode<T> {
value: T
children?: TreeNode<T>[]
}
// Deep readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
```
## Type Compatibility
### Structural Typing
```typescript
interface Point {
x: number
y: number
}
interface Named {
name: string
}
// Compatible if structure matches
let point: Point = { x: 0, y: 0 }
let namedPoint = { x: 0, y: 0, name: 'origin' }
point = namedPoint // OK: namedPoint has x and y
```
### Variance
**Covariance** (return types):
```typescript
interface Animal {
name: string
}
interface Dog extends Animal {
breed: string
}
let getDog: () => Dog
let getAnimal: () => Animal
getAnimal = getDog // OK: Dog is assignable to Animal
```
**Contravariance** (parameter types):
```typescript
let handleAnimal: (animal: Animal) => void
let handleDog: (dog: Dog) => void
handleDog = handleAnimal // OK: can pass Dog to function expecting Animal
```
## Index Types
### Index Signatures
```typescript
// String index
interface StringMap {
[key: string]: string
}
// Number index
interface NumberArray {
[index: number]: number
}
// Combine with named properties
interface MixedInterface {
length: number
[index: number]: string
}
```
### keyof Operator
```typescript
interface Person {
name: string
age: number
}
type PersonKeys = keyof Person // "name" | "age"
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
```
### Indexed Access Types
```typescript
interface Person {
name: string
age: number
address: {
street: string
city: string
}
}
type Name = Person['name'] // string
type Age = Person['age'] // number
type Address = Person['address'] // { street: string; city: string }
type AddressCity = Person['address']['city'] // string
// Access multiple keys
type NameOrAge = Person['name' | 'age'] // string | number
```
## Branded Types
```typescript
// Create nominal types from structural types
type Brand<K, T> = K & { __brand: T }
type USD = Brand<number, 'USD'>
type EUR = Brand<number, 'EUR'>
function makeUSD(amount: number): USD {
return amount as USD
}
function makeEUR(amount: number): EUR {
return amount as EUR
}
let usd = makeUSD(100)
let eur = makeEUR(100)
// usd = eur // Error: different brands
```
## Best Practices
1. **Prefer type inference** - Let TypeScript infer types when obvious
2. **Use strict null checks** - Enable strictNullChecks for better safety
3. **Avoid `any`** - Use `unknown` and narrow with type guards
4. **Use discriminated unions** - Better than loose unions for state
5. **Leverage const assertions** - Get narrow literal types
6. **Use branded types** - When structural typing isn't enough
7. **Document complex types** - Add JSDoc comments
8. **Extract reusable types** - DRY principle applies to types too
9. **Use utility types** - Leverage built-in transformation types
10. **Test your types** - Use type assertions to verify type correctness

View File

@@ -0,0 +1,666 @@
# TypeScript Utility Types Reference
TypeScript provides several built-in utility types that help transform and manipulate types. These are implemented using advanced type features like mapped types and conditional types.
## Property Modifiers
### Partial\<T\>
Makes all properties in `T` optional.
```typescript
interface User {
id: string
name: string
email: string
age: number
}
type PartialUser = Partial<User>
// {
// id?: string
// name?: string
// email?: string
// age?: number
// }
// Useful for update operations
function updateUser(id: string, updates: Partial<User>) {
// Only update provided fields
}
updateUser('123', { name: 'Alice' }) // OK
updateUser('123', { name: 'Alice', age: 30 }) // OK
```
### Required\<T\>
Makes all properties in `T` required (removes optionality).
```typescript
interface Config {
host?: string
port?: number
timeout?: number
}
type RequiredConfig = Required<Config>
// {
// host: string
// port: number
// timeout: number
// }
function initServer(config: RequiredConfig) {
// All properties are guaranteed to exist
console.log(config.host, config.port, config.timeout)
}
```
### Readonly\<T\>
Makes all properties in `T` readonly.
```typescript
interface MutablePoint {
x: number
y: number
}
type ImmutablePoint = Readonly<MutablePoint>
// {
// readonly x: number
// readonly y: number
// }
const point: ImmutablePoint = { x: 0, y: 0 }
// point.x = 10 // Error: Cannot assign to 'x' because it is a read-only property
```
### Mutable\<T\> (Custom)
Removes readonly modifiers (not built-in, but useful pattern).
```typescript
type Mutable<T> = {
-readonly [K in keyof T]: T[K]
}
interface ReadonlyPerson {
readonly name: string
readonly age: number
}
type MutablePerson = Mutable<ReadonlyPerson>
// {
// name: string
// age: number
// }
```
## Property Selection
### Pick\<T, K\>
Creates a type by picking specific properties from `T`.
```typescript
interface User {
id: string
name: string
email: string
password: string
createdAt: Date
}
type UserProfile = Pick<User, 'id' | 'name' | 'email'>
// {
// id: string
// name: string
// email: string
// }
// Useful for API responses
function getUserProfile(id: string): UserProfile {
// Return only safe properties
}
```
### Omit\<T, K\>
Creates a type by omitting specific properties from `T`.
```typescript
interface User {
id: string
name: string
email: string
password: string
}
type UserWithoutPassword = Omit<User, 'password'>
// {
// id: string
// name: string
// email: string
// }
// Useful for public user data
function publishUser(user: User): UserWithoutPassword {
const { password, ...publicData } = user
return publicData
}
```
## Union Type Utilities
### Exclude\<T, U\>
Excludes types from `T` that are assignable to `U`.
```typescript
type T1 = Exclude<'a' | 'b' | 'c', 'a'> // "b" | "c"
type T2 = Exclude<string | number | boolean, boolean> // string | number
type EventType = 'click' | 'scroll' | 'mousemove' | 'keypress'
type UIEvent = Exclude<EventType, 'scroll'> // "click" | "mousemove" | "keypress"
```
### Extract\<T, U\>
Extracts types from `T` that are assignable to `U`.
```typescript
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'> // "a"
type T2 = Extract<string | number | boolean, boolean> // boolean
type Shape = 'circle' | 'square' | 'triangle' | 'rectangle'
type RoundedShape = Extract<Shape, 'circle'> // "circle"
```
### NonNullable\<T\>
Excludes `null` and `undefined` from `T`.
```typescript
type T1 = NonNullable<string | null | undefined> // string
type T2 = NonNullable<string | number | null> // string | number
function processValue(value: string | null | undefined) {
if (value !== null && value !== undefined) {
const nonNull: NonNullable<typeof value> = value
// nonNull is guaranteed to be string
}
}
```
## Object Construction
### Record\<K, T\>
Constructs an object type with keys of type `K` and values of type `T`.
```typescript
type PageInfo = Record<string, number>
// { [key: string]: number }
const pages: PageInfo = {
home: 1,
about: 2,
contact: 3,
}
// Useful for mapped objects
type UserRole = 'admin' | 'user' | 'guest'
type RolePermissions = Record<UserRole, string[]>
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read'],
}
// With specific keys
type ThemeColors = Record<'primary' | 'secondary' | 'accent', string>
const colors: ThemeColors = {
primary: '#007bff',
secondary: '#6c757d',
accent: '#28a745',
}
```
## Function Utilities
### Parameters\<T\>
Extracts the parameter types of a function type as a tuple.
```typescript
function createUser(name: string, age: number, email: string) {
// ...
}
type CreateUserParams = Parameters<typeof createUser>
// [name: string, age: number, email: string]
// Useful for higher-order functions
function withLogging<T extends (...args: any[]) => any>(
fn: T,
...args: Parameters<T>
): ReturnType<T> {
console.log('Calling with:', args)
return fn(...args)
}
```
### ConstructorParameters\<T\>
Extracts the parameter types of a constructor function type.
```typescript
class User {
constructor(public name: string, public age: number) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>
// [name: string, age: number]
function createUser(...args: UserConstructorParams): User {
return new User(...args)
}
```
### ReturnType\<T\>
Extracts the return type of a function type.
```typescript
function createUser() {
return {
id: '123',
name: 'Alice',
email: 'alice@example.com',
}
}
type User = ReturnType<typeof createUser>
// {
// id: string
// name: string
// email: string
// }
// Useful with async functions
async function fetchData() {
return { success: true, data: [1, 2, 3] }
}
type FetchResult = ReturnType<typeof fetchData>
// Promise<{ success: boolean; data: number[] }>
type UnwrappedResult = Awaited<FetchResult>
// { success: boolean; data: number[] }
```
### InstanceType\<T\>
Extracts the instance type of a constructor function type.
```typescript
class User {
name: string
constructor(name: string) {
this.name = name
}
}
type UserInstance = InstanceType<typeof User>
// User
function processUser(user: UserInstance) {
console.log(user.name)
}
```
### ThisParameterType\<T\>
Extracts the type of the `this` parameter for a function type.
```typescript
function toHex(this: Number) {
return this.toString(16)
}
type ThisType = ThisParameterType<typeof toHex> // Number
```
### OmitThisParameter\<T\>
Removes the `this` parameter from a function type.
```typescript
function toHex(this: Number) {
return this.toString(16)
}
type PlainFunction = OmitThisParameter<typeof toHex>
// () => string
```
## String Manipulation
### Uppercase\<S\>
Converts string literal type to uppercase.
```typescript
type Greeting = 'hello'
type LoudGreeting = Uppercase<Greeting> // "HELLO"
// Useful for constants
type HttpMethod = 'get' | 'post' | 'put' | 'delete'
type HttpMethodUppercase = Uppercase<HttpMethod>
// "GET" | "POST" | "PUT" | "DELETE"
```
### Lowercase\<S\>
Converts string literal type to lowercase.
```typescript
type Greeting = 'HELLO'
type QuietGreeting = Lowercase<Greeting> // "hello"
```
### Capitalize\<S\>
Capitalizes the first letter of a string literal type.
```typescript
type Event = 'click' | 'scroll' | 'mousemove'
type EventHandler = `on${Capitalize<Event>}`
// "onClick" | "onScroll" | "onMousemove"
```
### Uncapitalize\<S\>
Uncapitalizes the first letter of a string literal type.
```typescript
type Greeting = 'Hello'
type LowerGreeting = Uncapitalize<Greeting> // "hello"
```
## Async Utilities
### Awaited\<T\>
Unwraps the type of a Promise (recursively).
```typescript
type T1 = Awaited<Promise<string>> // string
type T2 = Awaited<Promise<Promise<number>>> // number
type T3 = Awaited<boolean | Promise<string>> // boolean | string
// Useful with async functions
async function fetchUser() {
return { id: '123', name: 'Alice' }
}
type User = Awaited<ReturnType<typeof fetchUser>>
// { id: string; name: string }
```
## Custom Utility Types
### DeepPartial\<T\>
Makes all properties and nested properties optional.
```typescript
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
interface User {
id: string
profile: {
name: string
address: {
street: string
city: string
}
}
}
type PartialUser = DeepPartial<User>
// All properties at all levels are optional
```
### DeepReadonly\<T\>
Makes all properties and nested properties readonly.
```typescript
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
interface User {
id: string
profile: {
name: string
address: {
street: string
city: string
}
}
}
type ImmutableUser = DeepReadonly<User>
// All properties at all levels are readonly
```
### PartialBy\<T, K\>
Makes specific properties optional.
```typescript
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
interface User {
id: string
name: string
email: string
age: number
}
type UserWithOptionalEmail = PartialBy<User, 'email' | 'age'>
// {
// id: string
// name: string
// email?: string
// age?: number
// }
```
### RequiredBy\<T, K\>
Makes specific properties required.
```typescript
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
interface User {
id?: string
name?: string
email?: string
}
type UserWithRequiredId = RequiredBy<User, 'id'>
// {
// id: string
// name?: string
// email?: string
// }
```
### PickByType\<T, U\>
Picks properties by their value type.
```typescript
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
interface User {
id: string
name: string
age: number
active: boolean
}
type StringProperties = PickByType<User, string>
// { id: string; name: string }
type NumberProperties = PickByType<User, number>
// { age: number }
```
### OmitByType\<T, U\>
Omits properties by their value type.
```typescript
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K]
}
interface User {
id: string
name: string
age: number
active: boolean
}
type NonStringProperties = OmitByType<User, string>
// { age: number; active: boolean }
```
### Prettify\<T\>
Flattens intersections for better IDE tooltips.
```typescript
type Prettify<T> = {
[K in keyof T]: T[K]
} & {}
type A = { a: string }
type B = { b: number }
type C = A & B
type PrettyC = Prettify<C>
// Displays as: { a: string; b: number }
// Instead of: A & B
```
### ValueOf\<T\>
Gets the union of all value types.
```typescript
type ValueOf<T> = T[keyof T]
interface Colors {
red: '#ff0000'
green: '#00ff00'
blue: '#0000ff'
}
type ColorValue = ValueOf<Colors>
// "#ff0000" | "#00ff00" | "#0000ff"
```
### Nullable\<T\>
Makes type nullable.
```typescript
type Nullable<T> = T | null
type NullableString = Nullable<string> // string | null
```
### Maybe\<T\>
Makes type nullable or undefined.
```typescript
type Maybe<T> = T | null | undefined
type MaybeString = Maybe<string> // string | null | undefined
```
### UnionToIntersection\<U\>
Converts union to intersection (advanced).
```typescript
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never
type Union = { a: string } | { b: number }
type Intersection = UnionToIntersection<Union>
// { a: string } & { b: number }
```
## Combining Utility Types
Utility types can be composed for powerful transformations:
```typescript
// Make specific properties optional and readonly
type PartialReadonly<T, K extends keyof T> = Readonly<Pick<T, K>> &
Partial<Omit<T, K>>
interface User {
id: string
name: string
email: string
password: string
}
type SafeUser = PartialReadonly<User, 'id' | 'name'>
// {
// readonly id: string
// readonly name: string
// email?: string
// password?: string
// }
// Pick and make readonly
type ReadonlyPick<T, K extends keyof T> = Readonly<Pick<T, K>>
// Omit and make required
type RequiredOmit<T, K extends keyof T> = Required<Omit<T, K>>
```
## Best Practices
1. **Use built-in utilities first** - They're well-tested and optimized
2. **Compose utilities** - Combine utilities for complex transformations
3. **Create custom utilities** - For patterns you use frequently
4. **Name utilities clearly** - Make intent obvious from the name
5. **Document complex utilities** - Add JSDoc for non-obvious transformations
6. **Test utility types** - Use type assertions to verify behavior
7. **Avoid over-engineering** - Don't create utilities for one-off uses
8. **Consider readability** - Sometimes explicit types are clearer
9. **Use Prettify** - For better IDE tooltips with intersections
10. **Leverage keyof** - For type-safe property selection