Skip to content
This repository was archived by the owner on Jul 11, 2025. It is now read-only.

Latest commit

 

History

History
1833 lines (1508 loc) · 54.9 KB

File metadata and controls

1833 lines (1508 loc) · 54.9 KB

Types: A Real-World Guide

Table of Contents

  1. Introduction & Core Concepts
  2. Basic Usage Patterns
  3. Real-World Examples
  4. Advanced Features Deep Dive
  5. Practical Patterns & Best Practices
  6. Integration Examples

Introduction & Core Concepts

The Problem Types Solves

Advanced TypeScript applications require sophisticated type manipulations, utility types, and type-level programming to ensure type safety and developer experience. Without proper utility types, developers often struggle with:

// Traditional approach - manual type manipulation and unsafe operations
interface User {
  readonly id: string
  readonly name: string
  readonly email: string
  readonly preferences: {
    readonly theme: 'light' | 'dark'
    readonly notifications: boolean
  }
}

// Manual readonly removal - error-prone and verbose
type MutableUser = {
  id: string
  name: string
  email: string
  preferences: {
    theme: 'light' | 'dark'
    notifications: boolean
  }
}

// Manual tuple construction - not type-safe
type ThreeStrings = [string, string, string]
type FourStrings = [string, string, string, string] // Must define each arity manually

// Manual tagged union handling - repetitive and error-prone
type ApiResponse = 
  | { type: 'success'; data: unknown }
  | { type: 'error'; message: string }
  | { type: 'loading' }

function handleResponse(response: ApiResponse) {
  // Manual type narrowing for each case
  if (response.type === 'success') {
    return response.data // Type narrowing works but is verbose
  }
  if (response.type === 'error') {
    throw new Error(response.message)
  }
  // Must handle all cases manually
}

// Complex type equality checks - impossible with standard TypeScript
type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false
// This approach is flawed and doesn't handle edge cases properly

This approach leads to:

  • Type Manipulation Complexity - Manual utility type creation is verbose and error-prone
  • Unsafe Type Operations - No reliable way to manipulate object mutability, extract union members, or perform type-level computations
  • Repetitive Boilerplate - Common patterns like tagged unions require manual handling for each type
  • Poor Type-Level Abstraction - No composable utilities for advanced type manipulations

The Types Solution

Effect's Types module provides battle-tested utility types for advanced TypeScript patterns, type-level programming, and safe type manipulations:

import { Types, Effect, Data } from "effect"

// Type-safe tuple construction
type ExactlyThreeStrings = Types.TupleOf<3, string>
const validTuple: ExactlyThreeStrings = ["a", "b", "c"] // ✅ Valid
// const invalidTuple: ExactlyThreeStrings = ["a", "b"] // ❌ Type error

// Flexible tuple with minimum length
type AtLeastTwoNumbers = Types.TupleOfAtLeast<2, number>
const validMin: AtLeastTwoNumbers = [1, 2, 3, 4, 5] // ✅ Valid

// Safe mutability transformation
interface ReadonlyConfig {
  readonly apiUrl: string
  readonly timeout: number
  readonly retries: number
}

type MutableConfig = Types.Mutable<ReadonlyConfig>
// { apiUrl: string; timeout: number; retries: number }

// Tagged union utilities
type ApiEvent = 
  | Data.TaggedEnum<{
      UserCreated: { userId: string; email: string }
      UserUpdated: { userId: string; changes: Record<string, unknown> }
      UserDeleted: { userId: string }
    }>

function processEvent(event: ApiEvent) {
  // Type-safe tag extraction and handling
  type EventTags = Types.Tags<ApiEvent> // "UserCreated" | "UserUpdated" | "UserDeleted"
  
  // Safe event extraction by tag
  type CreatedEvent = Types.ExtractTag<ApiEvent, "UserCreated">
  // { _tag: "UserCreated"; userId: string; email: string }
}

// Advanced type equality with proper edge case handling
type IsUserEqual = Types.Equals<User, User> // true
type IsDifferent = Types.Equals<User, string> // false

Key Concepts

Tuple Utilities: Type-safe construction of fixed-length and variable-length tuples with exact type constraints

Tagged Union Operations: Extract tags, filter by tag, and manipulate discriminated unions safely

Mutability Control: Transform readonly types to mutable and vice versa, including deep transformations

Type Equality: Reliable type comparison that handles complex cases and edge conditions

Merge Operations: Combine object types with configurable precedence rules

Variance Helpers: Utilities for managing covariance, contravariance, and invariance in generic types

Basic Usage Patterns

Pattern 1: Creating Type-Safe Tuples

import { Types } from "effect"

// Fixed-length tuples
type Coordinates2D = Types.TupleOf<2, number>
type Coordinates3D = Types.TupleOf<3, number>

const point2D: Coordinates2D = [10, 20] // ✅ Valid
const point3D: Coordinates3D = [10, 20, 30] // ✅ Valid

// Variable-length tuples with minimum requirements
type DatabaseRow = Types.TupleOfAtLeast<3, string | number>
const userRow: DatabaseRow = ["123", "Alice", "alice@test.com", 25, true] // ✅ Valid

// Practical function signature with tuple constraints
function createMatrix<N extends number>(size: N, defaultValue: number): Types.TupleOf<N, Types.TupleOf<N, number>> {
  // Implementation would create an N×N matrix
  return Array(size).fill(Array(size).fill(defaultValue)) as any
}

Pattern 2: Working with Tagged Unions

import { Types, Data } from "effect"

// Define a tagged union for application states
type AppState = 
  | Data.TaggedEnum<{
      Loading: {}
      Success: { data: unknown }
      Error: { error: string; retryable: boolean }
      Idle: {}
    }>

// Extract all possible tags
type StateTags = Types.Tags<AppState> // "Loading" | "Success" | "Error" | "Idle"

// Extract specific state by tag
type ErrorState = Types.ExtractTag<AppState, "Error">
// { _tag: "Error"; error: string; retryable: boolean }

// Exclude states by tag
type NonErrorStates = Types.ExcludeTag<AppState, "Error">
// Loading | Success | Idle states only

// Create type-safe state handlers
function handleAppState(state: AppState) {
  switch (state._tag) {
    case "Loading":
      return "Showing spinner..."
    case "Success":
      return `Data loaded: ${JSON.stringify(state.data)}`
    case "Error":
      return state.retryable ? 
        `Error (retryable): ${state.error}` : 
        `Fatal error: ${state.error}`
    case "Idle":
      return "Ready to load data"
  }
}

Pattern 3: Mutability Transformations

import { Types } from "effect"

// Configuration objects that need to be mutable for initialization
interface ReadonlyAppConfig {
  readonly database: {
    readonly host: string
    readonly port: number
    readonly credentials: {
      readonly username: string
      readonly password: string
    }
  }
  readonly cache: {
    readonly ttl: number
    readonly maxSize: number
  }
}

// Make all properties mutable for configuration setup
type MutableConfig = Types.Mutable<ReadonlyAppConfig>

// Deep mutability for nested readonly structures
type DeepMutableConfig = Types.DeepMutable<ReadonlyAppConfig>

// Practical configuration initialization
function initializeConfig(): ReadonlyAppConfig {
  const config: DeepMutableConfig = {
    database: {
      host: process.env.DB_HOST || "localhost",
      port: parseInt(process.env.DB_PORT || "5432"),
      credentials: {
        username: process.env.DB_USER || "user",
        password: process.env.DB_PASS || "password"
      }
    },
    cache: {
      ttl: 3600,
      maxSize: 1000
    }
  }

  // Return as readonly after initialization
  return config as ReadonlyAppConfig
}

Real-World Examples

Example 1: Type-Safe API Response Handler

Building a robust API client that handles different response types with complete type safety:

import { Types, Effect, Data } from "effect"

// Define API response types as tagged unions
type ApiResponse<T> = Data.TaggedEnum<{
  Success: { data: T; metadata: { requestId: string; timestamp: number } }
  ClientError: { status: 400 | 401 | 403 | 404; message: string; details?: Record<string, unknown> }
  ServerError: { status: 500 | 502 | 503; message: string; retryAfter?: number }
  NetworkError: { cause: "timeout" | "connection" | "dns"; originalError: Error }
}>

// Extract response tags for pattern matching
type ResponseTags<T> = Types.Tags<ApiResponse<T>>
// "Success" | "ClientError" | "ServerError" | "NetworkError"

// Type-safe response handlers using Types utilities
class ApiClient {
  private handleResponse<T>(response: ApiResponse<T>) {
    return Effect.gen(function* () {
      // Use Types.ExtractTag for type-safe extraction
      switch (response._tag) {
        case "Success": {
          const successResponse = response as Types.ExtractTag<ApiResponse<T>, "Success">
          yield* Effect.logInfo(`Request ${successResponse.metadata.requestId} succeeded`)
          return successResponse.data
        }
        
        case "ClientError": {
          const clientError = response as Types.ExtractTag<ApiResponse<T>, "ClientError">
          return yield* Effect.fail({
            type: "ClientError" as const,
            status: clientError.status,
            message: clientError.message,
            retryable: false
          })
        }
        
        case "ServerError": {
          const serverError = response as Types.ExtractTag<ApiResponse<T>, "ServerError">
          return yield* Effect.fail({
            type: "ServerError" as const,
            status: serverError.status,
            message: serverError.message,
            retryable: true,
            retryAfter: serverError.retryAfter
          })
        }
        
        case "NetworkError": {
          const networkError = response as Types.ExtractTag<ApiResponse<T>, "NetworkError">
          return yield* Effect.fail({
            type: "NetworkError" as const,
            cause: networkError.cause,
            retryable: networkError.cause !== "dns"
          })
        }
      }
    })
  }

  // Generic request method with full type safety
  public request<T>(url: string, options?: RequestInit): Effect.Effect<T, ApiError> {
    return Effect.gen(function* () {
      const response = yield* Effect.tryPromise({
        try: () => fetch(url, options),
        catch: (error): ApiResponse<T> => Data.tagged("NetworkError", {
          cause: "connection" as const,
          originalError: error as Error
        })
      })

      const apiResponse: ApiResponse<T> = yield* Effect.gen(function* () {
        if (response.ok) {
          const data = yield* Effect.tryPromise(() => response.json())
          return Data.tagged("Success", {
            data,
            metadata: {
              requestId: response.headers.get("x-request-id") || "unknown",
              timestamp: Date.now()
            }
          })
        }

        const errorData = yield* Effect.tryPromise(() => response.json())
        
        if (response.status >= 400 && response.status < 500) {
          return Data.tagged("ClientError", {
            status: response.status as 400 | 401 | 403 | 404,
            message: errorData.message || "Client error",
            details: errorData.details
          })
        }
        
        return Data.tagged("ServerError", {
          status: response.status as 500 | 502 | 503,
          message: errorData.message || "Server error",
          retryAfter: response.headers.get("retry-after") ? 
            parseInt(response.headers.get("retry-after")!) : undefined
        })
      }).pipe(
        Effect.catchAll(() => Effect.succeed(Data.tagged("NetworkError", {
          cause: "timeout" as const,
          originalError: new Error("Response parsing failed")
        })))
      )

      return yield* this.handleResponse(apiResponse)
    })
  }
}

// Usage with complete type safety
interface User {
  id: string
  name: string
  email: string
}

const apiClient = new ApiClient()

const getUserProgram = Effect.gen(function* () {
  const user = yield* apiClient.request<User>("/api/users/123")
  yield* Effect.logInfo(`Retrieved user: ${user.name}`)
  return user
}).pipe(
  Effect.catchAll(error => Effect.gen(function* () {
    yield* Effect.logError(`API request failed: ${error.message}`)
    if (error.retryable) {
      yield* Effect.logInfo("Error is retryable, implementing retry logic...")
    }
    return yield* Effect.fail(error)
  }))
)

Example 2: Database Schema Evolution with Type Safety

Managing database schema changes while maintaining type safety across application layers:

import { Types, Effect, Data } from "effect"

// Database schema versions using tuple utilities
type SchemaVersions = Types.TupleOfAtLeast<1, number> // At least version 1

// Schema evolution tracking
interface SchemaEvolution<V extends SchemaVersions> {
  readonly versions: V
  readonly current: V[number]
  readonly migrations: Types.TupleOf<V['length'], Migration>
}

// Migration definition using tagged unions
type Migration = Data.TaggedEnum<{
  AddColumn: { table: string; column: string; type: string; nullable: boolean }
  DropColumn: { table: string; column: string }
  CreateTable: { table: string; columns: Record<string, { type: string; nullable: boolean }> }
  CreateIndex: { table: string; columns: string[]; unique: boolean }
}>

// User table evolution example
interface UserV1 {
  readonly id: string
  readonly name: string
  readonly email: string
}

interface UserV2 extends UserV1 {
  readonly createdAt: Date
  readonly updatedAt: Date
}

interface UserV3 extends UserV2 {
  readonly profile: {
    readonly avatar?: string
    readonly bio?: string
  }
}

// Type-safe schema evolution manager
class SchemaManager<V extends SchemaVersions> {
  constructor(private evolution: SchemaEvolution<V>) {}

  // Apply migrations with full type safety
  public applyMigrations() {
    return Effect.gen(function* () {
      const migrations = this.evolution.migrations
      
      yield* Effect.logInfo(`Applying ${migrations.length} migrations`)
      
      // Process each migration with type safety
      for (const migration of migrations) {
        yield* this.executeMigration(migration)
      }
      
      yield* Effect.logInfo(`Schema updated to version ${this.evolution.current}`)
    }.bind(this))
  }

  private executeMigration(migration: Migration) {
    return Effect.gen(function* () {
      switch (migration._tag) {
        case "AddColumn": {
          const addCol = migration as Types.ExtractTag<Migration, "AddColumn">
          yield* Effect.logInfo(`Adding column ${addCol.column} to ${addCol.table}`)
          // Execute SQL: ALTER TABLE ${table} ADD COLUMN ${column} ${type}
          break
        }
        
        case "DropColumn": {
          const dropCol = migration as Types.ExtractTag<Migration, "DropColumn">
          yield* Effect.logInfo(`Dropping column ${dropCol.column} from ${dropCol.table}`)
          // Execute SQL: ALTER TABLE ${table} DROP COLUMN ${column}
          break
        }
        
        case "CreateTable": {
          const createTable = migration as Types.ExtractTag<Migration, "CreateTable">
          yield* Effect.logInfo(`Creating table ${createTable.table}`)
          // Execute SQL: CREATE TABLE ${table} (...)
          break
        }
        
        case "CreateIndex": {
          const createIndex = migration as Types.ExtractTag<Migration, "CreateIndex">
          const indexType = createIndex.unique ? "UNIQUE INDEX" : "INDEX"
          yield* Effect.logInfo(`Creating ${indexType} on ${createIndex.table}`)
          // Execute SQL: CREATE [UNIQUE] INDEX ON ${table} (${columns})
          break
        }
      }
    })
  }

  // Type-safe data transformation between schema versions
  public transformData<From, To>(
    data: From,
    fromVersion: number,
    toVersion: number
  ): Effect.Effect<To> {
    return Effect.gen(function* () {
      let current = data as any
      
      // Apply transformations step by step
      for (let version = fromVersion; version < toVersion; version++) {
        current = yield* this.transformVersion(current, version, version + 1)
      }
      
      return current as To
    })
  }

  private transformVersion(data: unknown, from: number, to: number) {
    return Effect.gen(function* () {
      yield* Effect.logDebug(`Transforming data from version ${from} to ${to}`)
      
      // Version-specific transformation logic
      if (from === 1 && to === 2) {
        return {
          ...data as UserV1,
          createdAt: new Date(),
          updatedAt: new Date()
        } as UserV2
      }
      
      if (from === 2 && to === 3) {
        return {
          ...data as UserV2,
          profile: {}
        } as UserV3
      }
      
      return data
    })
  }
}

// Usage with complete type safety
const userSchemaEvolution: SchemaEvolution<[1, 2, 3]> = {
  versions: [1, 2, 3],
  current: 3,
  migrations: [
    Data.tagged("AddColumn", { 
      table: "users", 
      column: "created_at", 
      type: "timestamp", 
      nullable: false 
    }),
    Data.tagged("AddColumn", { 
      table: "users", 
      column: "updated_at", 
      type: "timestamp", 
      nullable: false 
    }),
    Data.tagged("AddColumn", { 
      table: "users", 
      column: "profile", 
      type: "jsonb", 
      nullable: true 
    })
  ]
}

const schemaManager = new SchemaManager(userSchemaEvolution)

const migrationProgram = Effect.gen(function* () {
  yield* schemaManager.applyMigrations()
  
  // Transform legacy data
  const legacyUser: UserV1 = {
    id: "123",
    name: "Alice",
    email: "alice@example.com"
  }
  
  const modernUser = yield* schemaManager.transformData<UserV1, UserV3>(
    legacyUser, 
    1, 
    3
  )
  
  yield* Effect.logInfo(`Transformed user: ${JSON.stringify(modernUser)}`)
  return modernUser
})

Example 3: Type-Safe Configuration Merger

Building a configuration system that safely merges multiple configuration sources with type precedence:

import { Types, Effect, Config } from "effect"

// Configuration layers with different priorities
interface DatabaseConfig {
  readonly host: string
  readonly port: number
  readonly database: string
  readonly ssl: boolean
}

interface CacheConfig {
  readonly redis: {
    readonly host: string
    readonly port: number
  }
  readonly ttl: number
  readonly maxMemory: string
}

interface AppConfig {
  readonly environment: "development" | "staging" | "production"
  readonly debug: boolean
  readonly database: DatabaseConfig
  readonly cache: CacheConfig
}

// Configuration sources with different precedence
type DefaultConfig = Types.DeepMutable<AppConfig>
type EnvironmentConfig = Partial<Types.DeepMutable<AppConfig>>
type FileConfig = Partial<Types.DeepMutable<AppConfig>>
type RuntimeConfig = Partial<Types.DeepMutable<AppConfig>>

// Type-safe configuration merger using Types utilities
class ConfigurationManager {
  // Merge configurations with proper precedence (right takes priority)
  private mergeConfigs<A, B>(
    lower: A, 
    higher: B
  ): Types.MergeRight<A, B> {
    return this.deepMerge(lower, higher) as Types.MergeRight<A, B>
  }

  private deepMerge(target: any, source: any): any {
    const result = { ...target }
    
    Object.keys(source).forEach(key => {
      if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
        result[key] = this.deepMerge(target[key] || {}, source[key])
      } else if (source[key] !== undefined) {
        result[key] = source[key]
      }
    })
    
    return result
  }

  // Load and merge configurations in precedence order
  public loadConfiguration(): Effect.Effect<AppConfig> {
    return Effect.gen(function* () {
      // Default configuration (lowest priority)
      const defaultConfig: DefaultConfig = {
        environment: "development",
        debug: true,
        database: {
          host: "localhost",
          port: 5432,
          database: "app_db",
          ssl: false
        },
        cache: {
          redis: {
            host: "localhost",
            port: 6379
          },
          ttl: 3600,
          maxMemory: "100mb"
        }
      }

      // Environment-based configuration
      const envConfig: EnvironmentConfig = yield* this.loadEnvironmentConfig()
      
      // File-based configuration
      const fileConfig: FileConfig = yield* this.loadFileConfig()
      
      // Runtime configuration (highest priority)
      const runtimeConfig: RuntimeConfig = yield* this.loadRuntimeConfig()

      // Merge in precedence order using Types utilities
      let merged = defaultConfig
      merged = this.mergeConfigs(merged, envConfig)
      merged = this.mergeConfigs(merged, fileConfig)  
      merged = this.mergeConfigs(merged, runtimeConfig)

      // Validate final configuration
      const finalConfig = yield* this.validateConfiguration(merged)
      
      yield* Effect.logInfo("Configuration loaded and validated successfully")
      return finalConfig as AppConfig
    }.bind(this))
  }

  private loadEnvironmentConfig(): Effect.Effect<EnvironmentConfig> {
    return Effect.gen(function* () {
      const env = process.env
      
      const config: EnvironmentConfig = {}
      
      if (env.NODE_ENV) {
        config.environment = env.NODE_ENV as "development" | "staging" | "production"
      }
      
      if (env.DEBUG) {
        config.debug = env.DEBUG === "true"
      }
      
      if (env.DB_HOST || env.DB_PORT || env.DB_NAME) {
        config.database = {}
        if (env.DB_HOST) config.database.host = env.DB_HOST
        if (env.DB_PORT) config.database.port = parseInt(env.DB_PORT)
        if (env.DB_NAME) config.database.database = env.DB_NAME
        if (env.DB_SSL) config.database.ssl = env.DB_SSL === "true"
      }
      
      if (env.REDIS_HOST || env.REDIS_PORT) {
        config.cache = { redis: {} }
        if (env.REDIS_HOST) config.cache.redis.host = env.REDIS_HOST
        if (env.REDIS_PORT) config.cache.redis.port = parseInt(env.REDIS_PORT)
      }
      
      yield* Effect.logDebug(`Loaded environment config: ${JSON.stringify(config)}`)
      return config
    })
  }

  private loadFileConfig(): Effect.Effect<FileConfig> {
    return Effect.gen(function* () {
      const configPath = process.env.CONFIG_FILE || "./config.json"
      
      const fileContent = yield* Effect.tryPromise({
        try: () => import("fs").then(fs => fs.promises.readFile(configPath, "utf-8")),
        catch: () => new Error(`Failed to read config file: ${configPath}`)
      }).pipe(
        Effect.catchAll(() => Effect.succeed("{}")) // Default to empty config if file doesn't exist
      )
      
      const config = yield* Effect.try({
        try: () => JSON.parse(fileContent) as FileConfig,
        catch: () => new Error("Invalid JSON in config file")
      })
      
      yield* Effect.logDebug(`Loaded file config: ${JSON.stringify(config)}`)
      return config
    })
  }

  private loadRuntimeConfig(): Effect.Effect<RuntimeConfig> {
    return Effect.gen(function* () {
      // Runtime configuration could come from command line args, 
      // remote configuration service, etc.
      const config: RuntimeConfig = {}
      
      // Example: Override based on command line arguments
      if (process.argv.includes("--production")) {
        config.environment = "production"
        config.debug = false
      }
      
      yield* Effect.logDebug(`Loaded runtime config: ${JSON.stringify(config)}`)
      return config
    })
  }

  private validateConfiguration(config: any): Effect.Effect<AppConfig> {
    return Effect.gen(function* () {
      // Type-safe validation using Types utilities
      const requiredKeys = ["environment", "debug", "database", "cache"] as const
      
      for (const key of requiredKeys) {
        if (!Types.Has<typeof config, typeof key>) {
          return yield* Effect.fail(`Missing required configuration: ${key}`)
        }
      }
      
      // Validate environment values
      const validEnvironments = ["development", "staging", "production"] as const
      if (!validEnvironments.includes(config.environment)) {
        return yield* Effect.fail(`Invalid environment: ${config.environment}`)
      }
      
      // Validate database configuration
      if (!config.database.host || !config.database.database) {
        return yield* Effect.fail("Database configuration incomplete")
      }
      
      if (config.database.port < 1 || config.database.port > 65535) {
        return yield* Effect.fail("Invalid database port")
      }
      
      yield* Effect.logInfo("Configuration validation passed")
      return config as AppConfig
    })
  }
}

// Usage with complete type safety
const configManager = new ConfigurationManager()

const applicationProgram = Effect.gen(function* () {  
  const config = yield* configManager.loadConfiguration()
  
  yield* Effect.logInfo(`Starting application in ${config.environment} mode`)
  yield* Effect.logInfo(`Database: ${config.database.host}:${config.database.port}/${config.database.database}`)
  yield* Effect.logInfo(`Cache: ${config.cache.redis.host}:${config.cache.redis.port}`)
  
  return config
}).pipe(
  Effect.catchAll(error => Effect.gen(function* () {
    yield* Effect.logError(`Configuration loading failed: ${error}`)
    return yield* Effect.fail(error)
  }))
)

Advanced Features Deep Dive

Feature 1: Advanced Type Equality and Comparison

The Types module provides sophisticated type equality checking that handles complex edge cases:

Basic Type Equality Usage

import { Types } from "effect"

// Simple type equality
type IsStringString = Types.Equals<string, string> // true
type IsStringNumber = Types.Equals<string, number> // false

// Complex object equality
interface User { id: string; name: string }
interface SameUser { id: string; name: string }
interface DifferentUser { id: string; email: string }

type IsSameUser = Types.Equals<User, SameUser> // true
type IsDifferentUser = Types.Equals<User, DifferentUser> // false

Advanced Equality with Custom Return Types

import { Types } from "effect"

// Custom return types based on equality
type EqualityCheck<A, B> = Types.EqualsWith<A, B, "EQUAL", "NOT_EQUAL">

type StringEquality = EqualityCheck<string, string> // "EQUAL"
type MixedEquality = EqualityCheck<string, number> // "NOT_EQUAL"

// Conditional type definitions based on equality
type ConditionalApi<T, U> = Types.EqualsWith<T, U, 
  { type: "same"; api: "unified" }, 
  { type: "different"; api: "separated" }
>

type SameTypeApi = ConditionalApi<string, string>
// { type: "same"; api: "unified" }

type DifferentTypeApi = ConditionalApi<string, number>
// { type: "different"; api: "separated" }

Real-World Equality: API Version Compatibility

import { Types, Effect } from "effect"

// API version compatibility checker
interface ApiV1 {
  readonly version: "1.0"
  readonly user: { id: string; name: string }
}

interface ApiV2 {
  readonly version: "2.0"
  readonly user: { id: string; name: string; email: string }
}

interface ApiV2Compatible {
  readonly version: "2.0"
  readonly user: { id: string; name: string }
}

// Type-safe compatibility checking
type IsV1V2Compatible = Types.Equals<ApiV1['user'], ApiV2Compatible['user']> // true
type IsV1V2FullyCompatible = Types.Equals<ApiV1, ApiV2> // false

// Build compatibility layer
class ApiCompatibilityLayer {
  // Type-safe version handling
  public handleRequest<V1, V2>(
    v1Request: V1,
    v2Request: V2
  ): Effect.Effect<unknown> {
    return Effect.gen(function* () {
      type IsCompatible = Types.Equals<V1, V2>
      
      // Use type equality to determine handling strategy
      const isCompatible: IsCompatible = Types.Equals<V1, V2>
      
      if (isCompatible) {
        yield* Effect.logInfo("Versions are compatible - using unified handler")
        // Handle both with same logic
      } else {
        yield* Effect.logInfo("Versions differ - using compatibility layer")
        // Apply transformation logic
      }
    })
  }
}

Feature 2: Union to Intersection Transformation

Advanced type manipulation for converting unions to intersections:

Basic Union to Intersection

import { Types } from "effect"

// Convert union types to intersections
type Union = { a: string } | { b: number } | { c: boolean }
type Intersection = Types.UnionToIntersection<Union>
// { a: string } & { b: number } & { c: boolean }

// Function signature union to intersection
type FunctionUnion = 
  | ((x: string) => number)
  | ((x: number) => boolean)
  | ((x: boolean) => string)

type FunctionIntersection = Types.UnionToIntersection<FunctionUnion>
// ((x: string) => number) & ((x: number) => boolean) & ((x: boolean) => string)

Real-World Application: Plugin System

import { Types, Effect } from "effect"

// Plugin system with union to intersection
interface LoggerPlugin {
  log: (message: string) => Effect.Effect<void>
}

interface MetricsPlugin {
  increment: (metric: string) => Effect.Effect<void>
  gauge: (metric: string, value: number) => Effect.Effect<void>
}

interface AuthPlugin {
  authenticate: (token: string) => Effect.Effect<{ userId: string }>
  authorize: (userId: string, resource: string) => Effect.Effect<boolean>
}

// Plugin union
type PluginUnion = LoggerPlugin | MetricsPlugin | AuthPlugin

// Convert to intersection for combined functionality
type CombinedPlugins = Types.UnionToIntersection<PluginUnion>
// LoggerPlugin & MetricsPlugin & AuthPlugin

class PluginManager {
  private plugins: CombinedPlugins

  constructor(plugins: CombinedPlugins) {
    this.plugins = plugins
  }

  // Access all plugin functionality through single interface
  public createApplicationServices() {
    return Effect.gen(function* () {
      // All plugin methods available due to intersection type
      yield* this.plugins.log("Application starting")
      yield* this.plugins.increment("app.startup")
      
      const auth = yield* this.plugins.authenticate("sample-token")
      const authorized = yield* this.plugins.authorize(auth.userId, "dashboard")
      
      if (authorized) {
        yield* this.plugins.log(`User ${auth.userId} authorized for dashboard`)
        yield* this.plugins.gauge("active.users", 1)
      }
    }.bind(this))
  }
}

Feature 3: Advanced Mutability Control

Deep control over readonly/mutable transformations:

Deep Mutability Transformations

import { Types } from "effect"

// Complex nested readonly structure
interface DeepReadonlyConfig {
  readonly app: {
    readonly name: string
    readonly version: string
    readonly features: {
      readonly auth: {
        readonly enabled: boolean
        readonly providers: readonly string[]
      }
      readonly cache: {
        readonly type: "redis" | "memory"
        readonly config: {
          readonly ttl: number
          readonly maxSize: number
        }
      }
    }
  }
  readonly database: {
    readonly connections: readonly {
      readonly name: string
      readonly config: {
        readonly host: string
        readonly port: number
      }
    }[]
  }
}

// Transform to fully mutable
type MutableConfig = Types.DeepMutable<DeepReadonlyConfig>
// All readonly modifiers removed recursively

// Selective mutability control
type PartiallyMutable = Types.Mutable<DeepReadonlyConfig>
// Only top-level properties are mutable

Real-World Application: Configuration Builder

import { Types, Effect } from "effect"

// Configuration builder with progressive mutability
class ConfigurationBuilder {
  private config: Types.DeepMutable<DeepReadonlyConfig>

  constructor() {
    this.config = {
      app: {
        name: "",
        version: "1.0.0",
        features: {
          auth: {
            enabled: false,
            providers: []
          },
          cache: {
            type: "memory",
            config: {
              ttl: 3600,
              maxSize: 1000
            }
          }
        }
      },
      database: {
        connections: []
      }
    }
  }

  // Type-safe configuration methods
  public setAppName(name: string): this {
    this.config.app.name = name
    return this
  }

  public enableAuth(providers: string[]): this {
    this.config.app.features.auth.enabled = true
    this.config.app.features.auth.providers = providers
    return this
  }

  public addDatabaseConnection(name: string, host: string, port: number): this {
    this.config.database.connections.push({
      name,
      config: { host, port }
    })
    return this
  }

  public setCacheConfig(type: "redis" | "memory", ttl: number, maxSize: number): this {
    this.config.app.features.cache.type = type
    this.config.app.features.cache.config.ttl = ttl
    this.config.app.features.cache.config.maxSize = maxSize
    return this
  }

  // Build final readonly configuration
  public build(): Effect.Effect<DeepReadonlyConfig> {
    return Effect.gen(function* () {
      // Validate configuration
      if (!this.config.app.name) {
        return yield* Effect.fail("App name is required")
      }

      if (this.config.database.connections.length === 0) {
        return yield* Effect.fail("At least one database connection is required")
      }

      // Return as readonly
      return this.config as DeepReadonlyConfig
    }.bind(this))
  }
}

// Usage
const configProgram = Effect.gen(function* () {
  const config = yield* new ConfigurationBuilder()
    .setAppName("MyApp")
    .enableAuth(["google", "github"])
    .addDatabaseConnection("primary", "localhost", 5432)
    .setCacheConfig("redis", 7200, 2000)
    .build()

  yield* Effect.logInfo(`Built configuration: ${JSON.stringify(config)}`)
  return config
})

Practical Patterns & Best Practices

Pattern 1: Type-Safe Factory Pattern

import { Types, Effect, Data } from "effect"

// Factory configuration using utility types
interface FactoryConfig<T> {
  readonly name: string
  readonly version: string
  readonly create: () => Effect.Effect<T>
  readonly validate?: (instance: T) => Effect.Effect<boolean>
}

// Factory registry using tagged unions and type utilities
type ServiceFactories = Data.TaggedEnum<{
  Database: { config: FactoryConfig<DatabaseService> }
  Cache: { config: FactoryConfig<CacheService> }
  Logger: { config: FactoryConfig<LoggerService> }
}>

type FactoryTags = Types.Tags<ServiceFactories> // "Database" | "Cache" | "Logger"

// Type-safe factory manager
class ServiceFactory {
  private factories = new Map<FactoryTags, FactoryConfig<any>>()

  // Register factory with type safety
  public register<T, K extends FactoryTags>(
    type: K,
    config: FactoryConfig<T>
  ): Effect.Effect<void> {
    return Effect.gen(function* () {
      yield* Effect.logInfo(`Registering factory: ${type}`)
      this.factories.set(type, config)
    }.bind(this))
  }

  // Create service with type safety  
  public create<K extends FactoryTags>(
    type: K
  ): Effect.Effect<Types.ExtractTag<ServiceFactories, K> extends { config: FactoryConfig<infer T> } ? T : never> {
    return Effect.gen(function* () {
      const factory = this.factories.get(type)
      if (!factory) {
        return yield* Effect.fail(`No factory registered for: ${type}`)
      }

      yield* Effect.logInfo(`Creating service: ${type}`)
      const instance = yield* factory.create()
      
      if (factory.validate) {
        const isValid = yield* factory.validate(instance)
        if (!isValid) {
          return yield* Effect.fail(`Validation failed for: ${type}`)
        }
      }

      return instance as any
    }.bind(this))
  }
}

// Helper for creating type-safe factory configurations
const createFactoryConfig = <T>(config: FactoryConfig<T>): FactoryConfig<T> => config

// Usage with complete type safety
interface DatabaseService {
  query: (sql: string) => Effect.Effect<unknown[]>
  close: () => Effect.Effect<void>
}

interface CacheService {
  get: (key: string) => Effect.Effect<string | null>
  set: (key: string, value: string) => Effect.Effect<void>
}

interface LoggerService {
  info: (message: string) => Effect.Effect<void>
  error: (message: string) => Effect.Effect<void>
}

const serviceFactoryProgram = Effect.gen(function* () {
  const factory = new ServiceFactory()

  // Register factories
  yield* factory.register("Database", createFactoryConfig({
    name: "PostgreSQL",
    version: "1.0.0",
    create: () => Effect.succeed({
      query: (sql: string) => Effect.succeed([]),
      close: () => Effect.succeed(undefined)
    } as DatabaseService),
    validate: (db) => Effect.succeed(typeof db.query === 'function')
  }))

  yield* factory.register("Cache", createFactoryConfig({
    name: "Redis",
    version: "1.0.0", 
    create: () => Effect.succeed({
      get: (key: string) => Effect.succeed(null),
      set: (key: string, value: string) => Effect.succeed(undefined)
    } as CacheService)
  }))

  // Create services with type safety
  const database = yield* factory.create("Database") // DatabaseService
  const cache = yield* factory.create("Cache") // CacheService

  yield* Effect.logInfo("All services created successfully")
  return { database, cache }
})

Pattern 2: Configuration Inheritance Pattern

import { Types, Effect } from "effect"

// Base configuration interfaces
interface BaseConfig {
  readonly name: string
  readonly version: string
}

interface DatabaseConfig extends BaseConfig {
  readonly host: string
  readonly port: number
  readonly database: string
}

interface CacheConfig extends BaseConfig {
  readonly type: "redis" | "memory"
  readonly ttl: number
}

// Environment-specific overrides
interface DevelopmentOverrides {
  readonly debug: boolean
  readonly logLevel: "debug" | "info"
}

interface ProductionOverrides {
  readonly ssl: boolean
  readonly monitoring: boolean
}

// Type-safe configuration inheritance
type DevelopmentConfig<T extends BaseConfig> = Types.MergeRight<T, DevelopmentOverrides>
type ProductionConfig<T extends BaseConfig> = Types.MergeRight<T, ProductionOverrides>

// Configuration manager with inheritance
class ConfigManager {
  // Create environment-specific configurations
  public createDevelopmentConfig<T extends BaseConfig>(
    base: T
  ): Effect.Effect<DevelopmentConfig<T>> {
    return Effect.gen(function* () {
      const overrides: DevelopmentOverrides = {
        debug: true,
        logLevel: "debug"
      }

      const merged = this.mergeConfigs(base, overrides) as DevelopmentConfig<T>
      yield* Effect.logInfo("Created development configuration")
      return merged
    })
  }

  public createProductionConfig<T extends BaseConfig>(
    base: T
  ): Effect.Effect<ProductionConfig<T>> {
    return Effect.gen(function* () {
      const overrides: ProductionOverrides = {
        ssl: true,
        monitoring: true
      }

      const merged = this.mergeConfigs(base, overrides) as ProductionConfig<T>
      yield* Effect.logInfo("Created production configuration")
      return merged
    })
  }

  private mergeConfigs<A, B>(base: A, overrides: B): Types.MergeRight<A, B> {
    return { ...base, ...overrides } as Types.MergeRight<A, B>
  }
}

// Usage with type-safe inheritance
const configProgram = Effect.gen(function* () {
  const configManager = new ConfigManager()

  const baseDatabaseConfig: DatabaseConfig = {
    name: "primary-db",
    version: "1.0.0",
    host: "localhost",
    port: 5432,
    database: "app_db"
  }

  const baseCacheConfig: CacheConfig = {
    name: "cache",
    version: "1.0.0",
    type: "redis",
    ttl: 3600
  }

  // Environment-specific configurations
  const devDbConfig = yield* configManager.createDevelopmentConfig(baseDatabaseConfig)
  // DatabaseConfig & DevelopmentOverrides

  const prodCacheConfig = yield* configManager.createProductionConfig(baseCacheConfig)
  // CacheConfig & ProductionOverrides

  yield* Effect.logInfo(`Dev DB config has debug: ${devDbConfig.debug}`)
  yield* Effect.logInfo(`Prod cache config has monitoring: ${prodCacheConfig.monitoring}`)

  return { devDbConfig, prodCacheConfig }
})

Pattern 3: Type-Safe Event System

import { Types, Effect, Data } from "effect"

// Event system with tagged unions and type utilities
type SystemEvents = Data.TaggedEnum<{
  UserRegistered: { userId: string; email: string; timestamp: number }
  UserLoggedIn: { userId: string; sessionId: string; timestamp: number }
  OrderCreated: { orderId: string; userId: string; amount: number; timestamp: number }
  PaymentProcessed: { paymentId: string; orderId: string; status: "success" | "failed"; timestamp: number }
}>

type EventTags = Types.Tags<SystemEvents>
// "UserRegistered" | "UserLoggedIn" | "OrderCreated" | "PaymentProcessed"

// Event handler types
type EventHandler<T extends EventTags> = (
  event: Types.ExtractTag<SystemEvents, T>
) => Effect.Effect<void>

// Type-safe event bus
class EventBus {
  private handlers = new Map<EventTags, EventHandler<any>[]>()

  // Register type-safe event handlers
  public on<T extends EventTags>(
    eventType: T,
    handler: EventHandler<T>
  ): Effect.Effect<void> {
    return Effect.sync(() => {
      const existingHandlers = this.handlers.get(eventType) || []
      this.handlers.set(eventType, [...existingHandlers, handler])
    })
  }

  // Emit events with type safety
  public emit<T extends EventTags>(
    event: Types.ExtractTag<SystemEvents, T>
  ): Effect.Effect<void> {
    return Effect.gen(function* () {
      const handlers = this.handlers.get(event._tag) || []
      
      yield* Effect.logInfo(`Emitting event: ${event._tag}`)
      
      // Execute all handlers for this event type
      yield* Effect.all(
        handlers.map(handler => handler(event)),
        { concurrency: "unbounded" }
      )
    }.bind(this))
  }

  // Subscribe to multiple event types
  public onMultiple<T extends readonly EventTags[]>(
    eventTypes: T,
    handler: (event: Types.ExtractTag<SystemEvents, T[number]>) => Effect.Effect<void>
  ): Effect.Effect<void> {
    return Effect.all(
      eventTypes.map(eventType => 
        this.on(eventType, handler as EventHandler<any>)
      )
    ).pipe(Effect.map(() => {}))
  }
}

// Event handler implementations
const createEventHandlers = (eventBus: EventBus) => {
  return Effect.gen(function* () {
    // User-related event handlers
    yield* eventBus.on("UserRegistered", event => Effect.gen(function* () {
      yield* Effect.logInfo(`New user registered: ${event.email}`)
      // Send welcome email, create user profile, etc.
    }))

    yield* eventBus.on("UserLoggedIn", event => Effect.gen(function* () {
      yield* Effect.logInfo(`User logged in: ${event.userId}`)
      // Update last login time, track analytics, etc.
    }))

    // Order-related event handlers
    yield* eventBus.on("OrderCreated", event => Effect.gen(function* () {
      yield* Effect.logInfo(`Order created: ${event.orderId} for $${event.amount}`)
      // Send order confirmation, update inventory, etc.
    }))

    yield* eventBus.on("PaymentProcessed", event => Effect.gen(function* () {
      if (event.status === "success") {
        yield* Effect.logInfo(`Payment successful: ${event.paymentId}`)
        // Send receipt, fulfill order, etc.
      } else {
        yield* Effect.logError(`Payment failed: ${event.paymentId}`)
        // Send payment failure notification, retry logic, etc.
      }
    }))

    // Multi-event handler for analytics
    yield* eventBus.onMultiple(
      ["UserRegistered", "UserLoggedIn", "OrderCreated"] as const,
      event => Effect.gen(function* () {
        yield* Effect.logInfo(`Analytics: Processing ${event._tag} event`)
        // Send to analytics service
      })
    )
  })
}

// Usage with complete type safety
const eventSystemProgram = Effect.gen(function* () {
  const eventBus = new EventBus()
  
  // Set up event handlers
  yield* createEventHandlers(eventBus)

  // Emit events with type safety
  yield* eventBus.emit(Data.tagged("UserRegistered", {
    userId: "user123",
    email: "alice@example.com",
    timestamp: Date.now()
  }))

  yield* eventBus.emit(Data.tagged("OrderCreated", {
    orderId: "order456",
    userId: "user123",
    amount: 99.99,
    timestamp: Date.now()
  }))

  yield* eventBus.emit(Data.tagged("PaymentProcessed", {
    paymentId: "pay789",
    orderId: "order456",
    status: "success",
    timestamp: Date.now()
  }))

  yield* Effect.logInfo("All events processed successfully")
})

Integration Examples

Integration with Schema Validation

Combining Types utilities with Schema for advanced validation patterns:

import { Types, Effect, Schema, Data } from "effect"

// Schema-driven type generation with Types utilities
const BaseUserSchema = Schema.Struct({
  id: Schema.String,
  name: Schema.String,
  email: Schema.String
})

const UserPreferencesSchema = Schema.Struct({
  theme: Schema.Literal("light", "dark"),
  notifications: Schema.Boolean,
  language: Schema.String
})

// Combine schemas using Types utilities
type BaseUser = Schema.Schema.Type<typeof BaseUserSchema>
type UserPreferences = Schema.Schema.Type<typeof UserPreferencesSchema>
type UserWithPreferences = Types.MergeRight<BaseUser, { preferences: UserPreferences }>

// Dynamic schema composition
class SchemaComposer {
  // Merge schemas with type safety
  public static composeUserSchema<T extends Schema.Schema.All>(
    baseSchema: typeof BaseUserSchema,
    extensionSchema: T
  ): Schema.Schema<Types.MergeRight<BaseUser, Schema.Schema.Type<T>>> {
    return Schema.Struct({
      ...baseSchema.fields,
      ...extensionSchema.fields
    }) as any
  }

  // Create schema variants using Types utilities
  public static createMutableSchema<T extends Schema.Schema.All>(
    schema: T
  ): Schema.Schema<Types.Mutable<Schema.Schema.Type<T>>> {
    // Transform readonly schema to mutable version
    return schema as any
  }
}

// Usage with Effect integration
const schemaValidationProgram = Effect.gen(function* () {
  // Create extended user schema
  const ExtendedUserSchema = SchemaComposer.composeUserSchema(
    BaseUserSchema,
    Schema.Struct({ preferences: UserPreferencesSchema })
  )

  // Validate user data
  const userData = {
    id: "123",
    name: "Alice",
    email: "alice@example.com",
    preferences: {
      theme: "dark" as const,
      notifications: true,
      language: "en"
    }
  }

  const validatedUser = yield* Schema.decodeUnknown(ExtendedUserSchema)(userData)
  yield* Effect.logInfo(`Validated user: ${JSON.stringify(validatedUser)}`)

  return validatedUser
})

Integration with Effect Error Handling

Advanced error handling patterns using Types utilities:

import { Types, Effect, Data } from "effect"

// Error taxonomy using tagged unions
type ApplicationErrors = Data.TaggedEnum<{
  ValidationError: { field: string; message: string; value: unknown }
  DatabaseError: { operation: string; cause: string; retryable: boolean }
  NetworkError: { url: string; status?: number; timeout: boolean }
  AuthenticationError: { reason: "invalid_token" | "expired" | "missing" }
  AuthorizationError: { resource: string; action: string; userId: string }
}>

type ErrorTags = Types.Tags<ApplicationErrors>

// Error recovery strategies
class ErrorRecoveryManager {
  // Type-safe error classification
  public classifyError(error: ApplicationErrors): Effect.Effect<{
    severity: "low" | "medium" | "high"
    retryable: boolean
    userMessage: string
  }> {
    return Effect.gen(function* () {
      switch (error._tag) {
        case "ValidationError": {
          const validationError = error as Types.ExtractTag<ApplicationErrors, "ValidationError">
          return {
            severity: "low" as const,
            retryable: false,
            userMessage: `Invalid ${validationError.field}: ${validationError.message}`
          }
        }

        case "DatabaseError": {
          const dbError = error as Types.ExtractTag<ApplicationErrors, "DatabaseError">
          return {
            severity: "high" as const,
            retryable: dbError.retryable,
            userMessage: "A database error occurred. Please try again."
          }
        }

        case "NetworkError": {
          const networkError = error as Types.ExtractTag<ApplicationErrors, "NetworkError">
          return {
            severity: networkError.timeout ? "medium" : "high",
            retryable: true,
            userMessage: "Network connection failed. Please check your connection."
          }
        }

        case "AuthenticationError": {
          const authError = error as Types.ExtractTag<ApplicationErrors, "AuthenticationError">
          return {
            severity: "medium" as const,
            retryable: authError.reason === "expired",
            userMessage: "Authentication failed. Please log in again."
          }
        }

        case "AuthorizationError": {
          return {
            severity: "medium" as const,
            retryable: false,
            userMessage: "You don't have permission to perform this action."
          }
        }
      }
    })
  }

  // Recovery strategy based on error type
  public createRecoveryStrategy<T>(
    operation: Effect.Effect<T, ApplicationErrors>
  ): Effect.Effect<T, ApplicationErrors> {
    return operation.pipe(
      Effect.catchAll(error => Effect.gen(function* () {
        const classification = yield* this.classifyError(error)
        
        yield* Effect.logError(`Error occurred: ${error._tag}`)
        yield* Effect.logInfo(`Severity: ${classification.severity}, Retryable: ${classification.retryable}`)

        if (classification.retryable) {
          yield* Effect.logInfo("Attempting recovery...")
          // Implement retry logic based on error type
          return yield* this.attemptRecovery(operation, error)
        }

        // Non-retryable error - re-throw with context
        return yield* Effect.fail(error)
      }))
    )
  }

  private attemptRecovery<T>(
    operation: Effect.Effect<T, ApplicationErrors>,
    originalError: ApplicationErrors
  ): Effect.Effect<T, ApplicationErrors> {
    return Effect.gen(function* () {
      // Implement error-specific recovery logic
      switch (originalError._tag) {
        case "DatabaseError": {
          yield* Effect.sleep("1 second")
          return yield* operation
        }
        
        case "NetworkError": {
          yield* Effect.sleep("2 seconds")
          return yield* operation
        }
        
        default:
          return yield* Effect.fail(originalError)
      }
    })
  }
}

// Usage with comprehensive error handling
const errorHandlingProgram = Effect.gen(function* () {
  const recoveryManager = new ErrorRecoveryManager()

  // Simulate operations that might fail
  const riskyDatabaseOperation = Effect.gen(function* () {
    // Simulate database error
    return yield* Effect.fail(Data.tagged("DatabaseError", {
      operation: "select",
      cause: "Connection timeout",
      retryable: true
    }))
  })

  const riskyNetworkOperation = Effect.gen(function* () {
    // Simulate network error
    return yield* Effect.fail(Data.tagged("NetworkError", {
      url: "https://api.example.com/users",
      status: 503,
      timeout: false
    }))
  })

  // Apply recovery strategies
  const databaseResult = yield* recoveryManager.createRecoveryStrategy(riskyDatabaseOperation)
  const networkResult = yield* recoveryManager.createRecoveryStrategy(riskyNetworkOperation)

  return { databaseResult, networkResult }
}).pipe(
  Effect.catchAll(error => Effect.gen(function* () {
    yield* Effect.logError(`Unrecoverable error: ${error._tag}`)
    return null
  }))
)

Testing Strategies

Comprehensive testing patterns for Types-based code:

import { Types, Effect, TestClock, TestContext } from "effect"

// Type-safe test utilities
class TypesTestUtils {
  // Compile-time type equality assertions
  public static assertTypeEquals<A, B>(): Types.Equals<A, B> extends true ? void : never {
    // This will cause a compile error if types are not equal
    return undefined as any
  }

  // Runtime type compatibility checking
  public static checkTypeCompatibility<A, B>(
    valueA: A,
    valueB: B
  ): Effect.Effect<boolean> {
    return Effect.sync(() => {
      // Use JSON serialization for basic structural comparison
      try {
        const serializedA = JSON.stringify(valueA)
        const serializedB = JSON.stringify(valueB)
        return typeof valueA === typeof valueB
      } catch {
        return false
      }
    })
  }

  // Property-based testing for type transformations
  public static testMutabilityTransformation<T>(
    readonlyValue: T
  ): Effect.Effect<boolean> {
    return Effect.sync(() => {
      type MutableVersion = Types.Mutable<T>
      
      // Verify that mutable version has same keys but without readonly
      const original = readonlyValue as any
      const keys = Object.keys(original)
      
      // Test that transformation maintains structure
      return keys.length > 0 // Basic structural check
    })
  }
}

// Test suite for Types utilities
const typesTestSuite = Effect.gen(function* () {
  yield* Effect.logInfo("Running Types utility tests...")

  // Compile-time type equality tests
  TypesTestUtils.assertTypeEquals<string, string>() // ✅ Should compile
  // TypesTestUtils.assertTypeEquals<string, number>() // ❌ Would cause compile error

  // Test tuple type generation
  const testTuple: Types.TupleOf<3, string> = ["a", "b", "c"]
  yield* Effect.logInfo(`Tuple test passed: ${JSON.stringify(testTuple)}`)

  // Test mutability transformation
  interface TestReadonly {
    readonly prop: string
    readonly nested: {
      readonly value: number
    }
  }

  const readonlyObj: TestReadonly = {
    prop: "test",
    nested: { value: 42 }
  }

  const mutabilityTest = yield* TypesTestUtils.testMutabilityTransformation(readonlyObj)
  yield* Effect.logInfo(`Mutability transformation test: ${mutabilityTest ? "PASS" : "FAIL"}`)

  // Test tagged union operations
  type TestUnion = { _tag: "A"; valueA: string } | { _tag: "B"; valueB: number }
  type ExtractedA = Types.ExtractTag<TestUnion, "A">
  type UnionTags = Types.Tags<TestUnion>

  // Runtime verification
  const taggedValue: TestUnion = { _tag: "A", valueA: "test" }
  const hasCorrectTag = taggedValue._tag === "A"
  yield* Effect.logInfo(`Tagged union test: ${hasCorrectTag ? "PASS" : "FAIL"}`)

  yield* Effect.logInfo("All Types utility tests completed")
  return "Tests completed successfully"
})

// Integration with Effect's testing framework
const testProgram = typesTestSuite.pipe(
  Effect.provide(TestContext.TestContext),
  Effect.catchAll(error => Effect.gen(function* () {
    yield* Effect.logError(`Test failed: ${JSON.stringify(error)}`)
    return "Tests failed"
  }))
)

Conclusion

Types provides essential utility types for advanced TypeScript patterns, type-level programming, and safe type manipulations in Effect applications.

Key benefits:

  • Type Safety: Compile-time guarantees for complex type operations and transformations
  • Composability: Utility types that work together to build sophisticated type systems
  • Developer Experience: Reduced boilerplate and improved IDE support for advanced TypeScript patterns

The Types module is particularly valuable when building type-safe APIs, configuration systems, plugin architectures, and any application requiring advanced type manipulations. It bridges the gap between TypeScript's type system and practical application development, providing battle-tested utilities for common type-level programming tasks.