+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 67 of 354

๐ŸŽจ Mapped Types: The Ultimate Type Transformer

Master TypeScript's mapped types to transform existing types with surgical precision and create powerful type utilities ๐Ÿš€

๐Ÿš€Intermediate
30 min read

Prerequisites

  • Understanding of conditional types ๐ŸŽฏ
  • Knowledge of keyof operator ๐Ÿ“
  • Familiarity with generics โšก

What you'll learn

  • Master mapped type syntax and patterns ๐Ÿ—๏ธ
  • Transform object types systematically ๐Ÿ”ง
  • Build powerful utility types ๐ŸŽจ
  • Create type-safe data transformations ๐Ÿ›

๐ŸŽฏ Introduction

Welcome to the amazing world of mapped types! ๐ŸŽ‰ Think of mapped types as TypeScriptโ€™s most powerful transformation tool ๐Ÿ”ง - they can take any existing type and systematically transform every property according to your rules, like having a magical assembly line for types!

Youโ€™re about to discover the secret behind many of TypeScriptโ€™s built-in utilities like Partial, Required, and Readonly. Whether youโ€™re building type-safe APIs ๐ŸŒ, creating data transformation utilities ๐Ÿ› ๏ธ, or just want to level up your type wizardry ๐Ÿง™โ€โ™‚๏ธ, mapped types will revolutionize how you think about type manipulation.

By the end of this tutorial, youโ€™ll be transforming types with the precision of a master craftsperson! โœจ Letโ€™s begin this transformative journey! ๐Ÿš€

๐Ÿ“š Understanding Mapped Types

๐Ÿค” What are Mapped Types?

Mapped types are like automated factories ๐Ÿญ that take an existing type and create a new type by systematically transforming each property. Think of them as โ€œfor loopsโ€ for types - they iterate over each property key and apply transformation rules.

The basic syntax looks like this:

type MappedType<T> = {
  [K in keyof T]: TransformationRule
}

This reads as: โ€œFor each key K in type T, create a new property with some transformation ruleโ€

๐ŸŽจ Your First Mapped Type

Letโ€™s start with a simple example:

// ๐ŸŽฏ Original interface
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// ๐Ÿช„ Make all properties optional
type PartialUser<T> = {
  [K in keyof T]?: T[K];  // The ? makes each property optional
};

// ๐Ÿงช Testing our mapped type
type OptionalUser = PartialUser<User>;
// Result: {
//   id?: number;
//   name?: string;
//   email?: string;
//   age?: number;
// }

// ๐Ÿš€ Make all properties readonly
type ReadonlyUser<T> = {
  readonly [K in keyof T]: T[K];  // The readonly makes each property immutable
};

// โœจ Testing readonly version
type ImmutableUser = ReadonlyUser<User>;
// Result: {
//   readonly id: number;
//   readonly name: string;
//   readonly email: string;
//   readonly age: number;
// }

๐Ÿ’ก The Magic: Mapped types automatically iterate over every property and apply your transformation!

๐Ÿ” Breaking Down the Syntax

type MyMappedType<T> = {
  [K in keyof T]: SomeType
//  ^   ^      ^     ^
//  |   |      |     โ””โ”€โ”€ The type for each property
//  |   |      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ All keys of type T
//  |   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ "in" operator for iteration
//  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ The property key variable
}

๐Ÿ”ง Basic Mapped Type Patterns

๐Ÿ“ Essential Transformation Recipes

Letโ€™s explore the most useful mapped type patterns:

// ๐ŸŽจ Make all properties strings (serialization)
type Stringify<T> = {
  [K in keyof T]: string;
};

// ๐Ÿงช Testing stringification
interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

type StringifiedProduct = Stringify<Product>;
// Result: {
//   id: string;
//   name: string;
//   price: string;
//   inStock: string;
// }

// ๐Ÿš€ Make all properties nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// ๐ŸŽฎ Testing nullable transformation
type NullableProduct = Nullable<Product>;
// Result: {
//   id: number | null;
//   name: string | null;
//   price: number | null;
//   inStock: boolean | null;
// }

// โœจ Wrap all properties in arrays
type Arrayify<T> = {
  [K in keyof T]: T[K][];
};

// ๐Ÿงช Testing arrayification
type ArrayProduct = Arrayify<Product>;
// Result: {
//   id: number[];
//   name: string[];
//   price: number[];
//   inStock: boolean[];
// }

๐ŸŽฏ Advanced Transformations

// ๐ŸŽจ Create getter functions for each property
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

// ๐Ÿงช Testing getter creation
type UserGetters = Getters<User>;
// Result: {
//   getId: () => number;
//   getName: () => string;
//   getEmail: () => string;
//   getAge: () => number;
// }

// ๐Ÿš€ Create setter functions for each property
type Setters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

// ๐ŸŽฎ Testing setter creation
type UserSetters = Setters<User>;
// Result: {
//   setId: (value: number) => void;
//   setName: (value: string) => void;
//   setEmail: (value: string) => void;
//   setAge: (value: number) => void;
// }

// โœจ Combine getters and setters
type GettersAndSetters<T> = Getters<T> & Setters<T>;

type UserAccessors = GettersAndSetters<User>;
// Result: Complete getter and setter interface!

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: API Response Transformer

Letโ€™s build a system to transform API responses safely:

// ๐ŸŽฏ Define different API response formats
interface RawApiUser {
  user_id: number;
  full_name: string;
  email_address: string;
  date_of_birth: string;  // ISO string from API
  is_active: boolean;
  created_at: string;     // ISO string
  updated_at: string;     // ISO string
}

interface RawApiProduct {
  product_id: number;
  product_name: string;
  price_cents: number;    // Price in cents
  category_id: number;
  is_available: boolean;
  created_date: string;   // Different date format
}

// ๐Ÿš€ Transform snake_case API keys to camelCase
type CamelCaseKeys<T> = {
  [K in keyof T as CamelCase<K & string>]: T[K];
};

// Helper type for camelCase conversion
type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
  ? `${P1}${Uppercase<P2>}${CamelCase<P3>}`
  : S;

// ๐Ÿงช Testing camelCase transformation
type CamelUser = CamelCaseKeys<RawApiUser>;
// Result: {
//   userId: number;
//   fullName: string;
//   emailAddress: string;
//   dateOfBirth: string;
//   isActive: boolean;
//   createdAt: string;
//   updatedAt: string;
// }

// ๐ŸŽจ Smart type transformation for frontend
type TransformApiResponse<T> = {
  [K in keyof T]: 
    // Transform date strings to Date objects
    T[K] extends string 
      ? K extends `${string}date${string}` | `${string}Date${string}` | `${string}_at`
        ? Date
        : T[K]
      // Transform price_cents to price (number to number, but semantically different)
      : K extends 'price_cents' | 'priceCents'
        ? number  // Will be divided by 100 in runtime
        : T[K];
};

// โœจ Complete transformation pipeline
type FrontendUser = TransformApiResponse<CamelCaseKeys<RawApiUser>>;
// Result: {
//   userId: number;
//   fullName: string;
//   emailAddress: string;
//   dateOfBirth: Date;    // Transformed!
//   isActive: boolean;
//   createdAt: Date;      // Transformed!
//   updatedAt: Date;      // Transformed!
// }

// ๐ŸŽฎ Type-safe transformer class
class ApiTransformer {
  // ๐Ÿš€ Transform user data with full type safety
  transformUser(rawUser: RawApiUser): FrontendUser {
    return {
      userId: rawUser.user_id,
      fullName: rawUser.full_name,
      emailAddress: rawUser.email_address,
      dateOfBirth: new Date(rawUser.date_of_birth),
      isActive: rawUser.is_active,
      createdAt: new Date(rawUser.created_at),
      updatedAt: new Date(rawUser.updated_at)
    } as FrontendUser;
  }

  // ๐ŸŽฏ Generic transformer for any API response
  transformResponse<T extends Record<string, any>>(
    rawData: T,
    transformer: (raw: T) => TransformApiResponse<CamelCaseKeys<T>>
  ): TransformApiResponse<CamelCaseKeys<T>> {
    return transformer(rawData);
  }
}

// ๐Ÿงช Usage example
const apiTransformer = new ApiTransformer();

const rawUserData: RawApiUser = {
  user_id: 123,
  full_name: "Alice Wonder",
  email_address: "[email protected]",
  date_of_birth: "1990-05-15T00:00:00Z",
  is_active: true,
  created_at: "2023-01-15T10:30:00Z",
  updated_at: "2023-12-01T14:45:00Z"
};

const frontendUser = apiTransformer.transformUser(rawUserData);
console.log("๐ŸŽ‰ Transformed user:", frontendUser);
// TypeScript knows frontendUser.dateOfBirth is a Date object!

๐ŸŽฎ Example 2: Form Validation Schema Generator

Letโ€™s create a type-safe form validation system:

// ๐Ÿ† Define form interfaces
interface UserRegistrationForm {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
  age: number;
  termsAccepted: boolean;
  newsletter: boolean;
}

interface ProductForm {
  name: string;
  description: string;
  price: number;
  category: string;
  tags: string[];
  featured: boolean;
}

// ๐ŸŽฏ Create validation rules for each field type
type ValidationRule<T> = 
  T extends string
    ? {
        required?: boolean;
        minLength?: number;
        maxLength?: number;
        pattern?: RegExp;
        custom?: (value: T) => string | null;
      }
    : T extends number
      ? {
          required?: boolean;
          min?: number;
          max?: number;
          integer?: boolean;
          custom?: (value: T) => string | null;
        }
      : T extends boolean
        ? {
            required?: boolean;
            mustBeTrue?: boolean;
            custom?: (value: T) => string | null;
          }
        : T extends any[]
          ? {
              required?: boolean;
              minItems?: number;
              maxItems?: number;
              custom?: (value: T) => string | null;
            }
          : {
              required?: boolean;
              custom?: (value: T) => string | null;
            };

// ๐Ÿš€ Generate validation schema for any form
type ValidationSchema<T> = {
  [K in keyof T]: ValidationRule<T[K]>;
};

// ๐ŸŽจ Generate error types for each field
type FormErrors<T> = {
  [K in keyof T]?: string;
};

// โœจ Create form state type
type FormState<T> = {
  values: T;
  errors: FormErrors<T>;
  touched: { [K in keyof T]?: boolean };
  isValid: boolean;
  isSubmitting: boolean;
};

// ๐Ÿงช Type-safe form validator
class FormValidator<T extends Record<string, any>> {
  constructor(private schema: ValidationSchema<T>) {}

  // ๐ŸŽฏ Validate individual field
  validateField<K extends keyof T>(
    fieldName: K,
    value: T[K]
  ): string | null {
    const rule = this.schema[fieldName];
    if (!rule) return null;

    // Required validation
    if (rule.required && (value === null || value === undefined || value === '')) {
      return `${String(fieldName)} is required`;
    }

    // Type-specific validations
    if (typeof value === 'string' && 'minLength' in rule) {
      if (rule.minLength && value.length < rule.minLength) {
        return `${String(fieldName)} must be at least ${rule.minLength} characters`;
      }
    }

    if (typeof value === 'number' && 'min' in rule) {
      if (rule.min !== undefined && value < rule.min) {
        return `${String(fieldName)} must be at least ${rule.min}`;
      }
    }

    if (typeof value === 'boolean' && 'mustBeTrue' in rule) {
      if (rule.mustBeTrue && !value) {
        return `${String(fieldName)} must be accepted`;
      }
    }

    // Custom validation
    if (rule.custom) {
      return rule.custom(value);
    }

    return null;
  }

  // ๐Ÿš€ Validate entire form
  validateForm(values: T): FormErrors<T> {
    const errors: FormErrors<T> = {};

    for (const fieldName in values) {
      const error = this.validateField(fieldName, values[fieldName]);
      if (error) {
        errors[fieldName] = error;
      }
    }

    return errors;
  }

  // โœจ Check if form is valid
  isFormValid(values: T): boolean {
    const errors = this.validateForm(values);
    return Object.keys(errors).length === 0;
  }
}

// ๐ŸŽฎ Usage example
const userRegistrationSchema: ValidationSchema<UserRegistrationForm> = {
  username: {
    required: true,
    minLength: 3,
    maxLength: 20,
    pattern: /^[a-zA-Z0-9_]+$/
  },
  email: {
    required: true,
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  },
  password: {
    required: true,
    minLength: 8,
    custom: (value) => {
      if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
        return "Password must contain uppercase, lowercase, and number";
      }
      return null;
    }
  },
  confirmPassword: {
    required: true,
    custom: (value) => {
      // Note: In real implementation, you'd have access to other form values
      return null; // Would compare with password field
    }
  },
  age: {
    required: true,
    min: 13,
    max: 120,
    integer: true
  },
  termsAccepted: {
    required: true,
    mustBeTrue: true
  },
  newsletter: {
    required: false
  }
};

// ๐Ÿงช Create validator and test
const userValidator = new FormValidator(userRegistrationSchema);

const testFormData: UserRegistrationForm = {
  username: "alice123",
  email: "[email protected]",
  password: "SecurePass123",
  confirmPassword: "SecurePass123",
  age: 25,
  termsAccepted: true,
  newsletter: false
};

const validationErrors = userValidator.validateForm(testFormData);
const isValid = userValidator.isFormValid(testFormData);

console.log("๐Ÿ” Validation errors:", validationErrors);
console.log("โœ… Form is valid:", isValid);

๐Ÿš€ Advanced Mapped Type Techniques

๐Ÿง™โ€โ™‚๏ธ Conditional Property Transformation

// ๐ŸŽจ Transform properties based on their types
type SmartTransform<T> = {
  [K in keyof T]: 
    T[K] extends string 
      ? `validated_${T[K]}`  // Prefix strings
      : T[K] extends number
        ? T[K] | null        // Make numbers nullable
        : T[K] extends boolean
          ? T[K]             // Keep booleans as-is
          : never;           // Exclude other types
};

// ๐Ÿงช Testing conditional transformation
interface MixedData {
  name: string;
  count: number;
  active: boolean;
  metadata: object;  // This will become never
}

type TransformedData = SmartTransform<MixedData>;
// Result: {
//   name: `validated_${string}`;
//   count: number | null;
//   active: boolean;
//   metadata: never;  // Effectively removed
// }

๐Ÿ—๏ธ Recursive Mapped Types

// ๐ŸŽฏ Deep readonly transformation
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object 
    ? DeepReadonly<T[K]>  // Recursively apply to nested objects
    : T[K];
};

// ๐Ÿงช Testing deep readonly
interface NestedData {
  user: {
    profile: {
      name: string;
      settings: {
        theme: string;
        notifications: boolean;
      };
    };
    posts: Array<{
      title: string;
      content: string;
    }>;
  };
}

type DeepReadonlyData = DeepReadonly<NestedData>;
// Result: All nested properties become readonly!

// ๐Ÿš€ Deep partial transformation
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object 
    ? DeepPartial<T[K]>
    : T[K];
};

type PartialNestedData = DeepPartial<NestedData>;
// Result: All nested properties become optional!

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Index Signature Issues

// โŒ Problem with index signatures
interface FlexibleObject {
  name: string;
  [key: string]: any;  // Index signature
}

type StrictTransform<T> = {
  [K in keyof T]: string;
};

type TransformedFlexible = StrictTransform<FlexibleObject>;
// Result: { [x: string]: string; name: string } - not what we wanted!

// โœ… Solution: Handle index signatures explicitly
type SafeTransform<T> = {
  [K in keyof T as K extends string ? K : never]: T[K] extends any ? string : never;
};

type SafeTransformedFlexible = SafeTransform<FlexibleObject>;
// Result: { name: string } - much cleaner!

๐Ÿคฏ Pitfall 2: Preserving Modifiers

// โŒ Losing optional modifiers
interface PartialUser {
  id: number;
  name?: string;
  email?: string;
}

type BadTransform<T> = {
  [K in keyof T]: T[K] | null;  // Lost the optional modifiers!
};

type LostModifiers = BadTransform<PartialUser>;
// Result: { id: number | null; name: string | null; email: string | null }
// The ? modifiers are gone!

// โœ… Preserve modifiers explicitly
type GoodTransform<T> = {
  [K in keyof T]?: T[K] | null;  // Explicitly preserve optionality
};

// โœ… Or use conditional preservation
type PreserveModifiers<T> = {
  [K in keyof T as T[K] extends undefined ? never : K]: T[K];
} & {
  [K in keyof T as T[K] extends undefined ? K : never]?: T[K];
};

๐Ÿ” Pitfall 3: Type Distribution Issues

// โŒ Unexpected distribution behavior
type DistributeProblem<T> = T extends any 
  ? { [K in keyof T]: T[K] }
  : never;

type UnionTest = DistributeProblem<{ a: string } | { b: number }>;
// Result distributes: { a: string } | { b: number } (might not be desired)

// โœ… Prevent distribution when needed
type NoDistribute<T> = [T] extends [any]
  ? { [K in keyof T]: T[K] }
  : never;

type NoDistributionTest = NoDistribute<{ a: string } | { b: number }>;
// Result: { a: string; b: number } (intersection, not union)

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Keep Transformations Simple: Start with basic patterns before attempting complex logic
  2. ๐Ÿ“ Use Descriptive Names: UserFormFields<T> is better than Transform<T>
  3. ๐Ÿ›ก๏ธ Handle Edge Cases: Consider never, unknown, and union types
  4. ๐ŸŽจ Preserve Type Information: Donโ€™t lose important modifiers like readonly or ?
  5. โœจ Test Your Patterns: Always verify your mapped types work as expected
  6. ๐Ÿ” Use Conditional Logic Sparingly: Too many conditions make types hard to understand

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Type-Safe Database ORM

Create a mapped type system for a type-safe database ORM:

๐Ÿ“‹ Requirements:

  • โœ… Transform database column types to TypeScript types
  • ๐Ÿ”ง Generate query builder methods for each column
  • ๐ŸŽจ Create type-safe WHERE clause builders
  • ๐Ÿ“Š Handle relationships between tables
  • โœจ Type-safe result mapping!

๐Ÿš€ Bonus Points:

  • Add transaction support with type safety
  • Create migration type checking
  • Build query optimization hints

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Database schema definitions
interface DatabaseColumns {
  id: number;
  created_at: Date;
  updated_at: Date;
}

interface UserTable extends DatabaseColumns {
  username: string;
  email: string;
  age: number;
  is_active: boolean;
  profile_json: object;
}

interface PostTable extends DatabaseColumns {
  title: string;
  content: string;
  author_id: number;  // Foreign key to UserTable
  published: boolean;
  view_count: number;
}

// ๐Ÿš€ Transform database types to TypeScript types
type DatabaseToTypescript<T> = {
  [K in keyof T]: 
    T[K] extends number ? number :
    T[K] extends string ? string :
    T[K] extends boolean ? boolean :
    T[K] extends Date ? Date :
    T[K] extends object ? any :
    T[K];
};

// ๐ŸŽจ Generate query builder methods
type QueryMethods<T> = {
  [K in keyof T as `where${Capitalize<string & K>}`]: (
    operator: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'LIKE',
    value: T[K]
  ) => QueryBuilder<T>;
} & {
  [K in keyof T as `orderBy${Capitalize<string & K>}`]: (
    direction?: 'ASC' | 'DESC'
  ) => QueryBuilder<T>;
};

// โœจ Create select field options
type SelectFields<T> = {
  [K in keyof T]?: boolean;
} | '*';

// ๐Ÿงช Type-safe query builder
class QueryBuilder<T extends Record<string, any>> {
  private tableName: string;
  private whereConditions: string[] = [];
  private orderConditions: string[] = [];
  private selectFields: string[] = [];

  constructor(tableName: string) {
    this.tableName = tableName;
  }

  // ๐ŸŽฏ Type-safe WHERE clause building
  where<K extends keyof T>(
    field: K,
    operator: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'LIKE',
    value: T[K]
  ): this {
    this.whereConditions.push(`${String(field)} ${operator} ${JSON.stringify(value)}`);
    return this;
  }

  // ๐Ÿš€ Type-safe ORDER BY
  orderBy<K extends keyof T>(
    field: K,
    direction: 'ASC' | 'DESC' = 'ASC'
  ): this {
    this.orderConditions.push(`${String(field)} ${direction}`);
    return this;
  }

  // ๐ŸŽจ Type-safe SELECT fields
  select<K extends keyof T>(
    fields: K[] | '*'
  ): Pick<T, K extends keyof T ? K : never>[] {
    if (fields === '*') {
      this.selectFields = ['*'];
    } else {
      this.selectFields = fields.map(String);
    }
    
    // Mock implementation - would execute actual SQL
    console.log(`๐Ÿ“Š SELECT ${this.selectFields.join(', ')} FROM ${this.tableName}`);
    if (this.whereConditions.length > 0) {
      console.log(`๐Ÿ“‹ WHERE ${this.whereConditions.join(' AND ')}`);
    }
    if (this.orderConditions.length > 0) {
      console.log(`๐Ÿ“ˆ ORDER BY ${this.orderConditions.join(', ')}`);
    }
    
    return [] as any; // Mock return
  }

  // โœจ Get first result
  first(): T | null {
    console.log(`๐ŸŽฏ ${this.buildQuery()} LIMIT 1`);
    return null; // Mock return
  }

  // ๐Ÿ” Count results
  count(): number {
    console.log(`๐Ÿ”ข SELECT COUNT(*) FROM ${this.tableName}`);
    return 0; // Mock return
  }

  private buildQuery(): string {
    let query = `SELECT * FROM ${this.tableName}`;
    if (this.whereConditions.length > 0) {
      query += ` WHERE ${this.whereConditions.join(' AND ')}`;
    }
    if (this.orderConditions.length > 0) {
      query += ` ORDER BY ${this.orderConditions.join(', ')}`;
    }
    return query;
  }
}

// ๐ŸŽฎ Database ORM class
class TypeSafeORM {
  // ๐Ÿš€ Create table query builder
  table<T extends Record<string, any>>(tableName: string): QueryBuilder<T> {
    return new QueryBuilder<T>(tableName);
  }

  // ๐ŸŽฏ Type-safe table methods
  users(): QueryBuilder<UserTable> {
    return new QueryBuilder<UserTable>('users');
  }

  posts(): QueryBuilder<PostTable> {
    return new QueryBuilder<PostTable>('posts');
  }

  // โœจ Join operations with type safety
  join<T1 extends Record<string, any>, T2 extends Record<string, any>>(
    table1: QueryBuilder<T1>,
    table2: QueryBuilder<T2>,
    condition: string
  ): QueryBuilder<T1 & T2> {
    // Mock implementation for joins
    console.log(`๐Ÿ”— JOIN operation: ${condition}`);
    return new QueryBuilder<T1 & T2>('joined_tables');
  }
}

// ๐Ÿงช Usage examples
const orm = new TypeSafeORM();

// โœ… Type-safe queries
const activeUsers = orm.users()
  .where('is_active', '=', true)  // TypeScript knows is_active is boolean
  .where('age', '>=', 18)         // TypeScript knows age is number
  .orderBy('created_at', 'DESC')  // TypeScript knows created_at exists
  .select(['username', 'email']);  // TypeScript enforces valid field names

const recentPosts = orm.posts()
  .where('published', '=', true)
  .where('view_count', '>', 100)
  .orderBy('created_at', 'DESC')
  .select('*');

// โŒ These would cause TypeScript errors:
// orm.users().where('invalid_field', '=', 'value');  // invalid_field doesn't exist
// orm.users().where('age', '=', 'string');           // age should be number
// orm.posts().orderBy('nonexistent_field');          // field doesn't exist

console.log("๐ŸŽ‰ Type-safe ORM queries executed!");

// ๐Ÿš€ Advanced: Generate model classes from table types
type ModelMethods<T> = {
  save(): Promise<T>;
  delete(): Promise<boolean>;
  update(data: Partial<T>): Promise<T>;
  reload(): Promise<T>;
} & {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
} & {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

class BaseModel<T extends Record<string, any>> implements ModelMethods<T> {
  constructor(private data: T) {}

  save(): Promise<T> {
    console.log("๐Ÿ’พ Saving model...");
    return Promise.resolve(this.data);
  }

  delete(): Promise<boolean> {
    console.log("๐Ÿ—‘๏ธ Deleting model...");
    return Promise.resolve(true);
  }

  update(data: Partial<T>): Promise<T> {
    console.log("โœ๏ธ Updating model...", data);
    Object.assign(this.data, data);
    return Promise.resolve(this.data);
  }

  reload(): Promise<T> {
    console.log("๐Ÿ”„ Reloading model...");
    return Promise.resolve(this.data);
  }

  // Dynamic getter/setter generation would happen here
  [key: string]: any;
}

// ๐ŸŽฏ Specific model classes
class User extends BaseModel<UserTable> {
  getId() { return this.data.id; }
  getUsername() { return this.data.username; }
  getEmail() { return this.data.email; }
  
  setUsername(username: string) { this.data.username = username; }
  setEmail(email: string) { this.data.email = email; }
  
  private data!: UserTable; // Override for proper typing
}

// Usage
const user = new User({
  id: 1,
  username: "alice",
  email: "[email protected]",
  age: 30,
  is_active: true,
  profile_json: {},
  created_at: new Date(),
  updated_at: new Date()
});

console.log("๐Ÿ‘ค User model created:", user.getUsername());

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered mapped types! Hereโ€™s what you can now do:

  • โœ… Transform any object type systematically with surgical precision ๐Ÿ’ช
  • โœ… Build powerful utility types like the TypeScript built-ins ๐Ÿ›ก๏ธ
  • โœ… Create type-safe data transformations for APIs and forms ๐ŸŽฏ
  • โœ… Handle complex type manipulations with confidence ๐Ÿ›
  • โœ… Design elegant type systems that scale beautifully ๐Ÿš€

Remember: Mapped types are like having a magic wand โœจ that can transform any type according to your exact specifications!

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve conquered mapped types!

Hereโ€™s what to explore next:

  1. ๐Ÿ’ป Practice with the ORM exercise above - try different table schemas
  2. ๐Ÿ—๏ธ Build your own mapped type utilities for real projects
  3. ๐Ÿ“š Move on to our next tutorial: Key Remapping in Mapped Types: Advanced Transformations
  4. ๐ŸŒŸ Share your mapped type creations with the TypeScript community!

You now possess one of TypeScriptโ€™s most powerful transformation tools. Use it to create type systems that are both elegant and practical. Remember - every type transformation expert started with curiosity. Keep experimenting, keep learning, and most importantly, have fun transforming types! ๐Ÿš€โœจ


Happy type transforming! ๐ŸŽ‰๐ŸŽจโœจ