Skip to content

proxied

Create a proxy function that can intercept and modify function calls, parameters, and return values.

Basic Usage

typescript
import { proxied } from 'radash'

const originalFn = (a: number, b: number) => a + b
const proxiedFn = proxied(originalFn, {
  before: (args) => console.log('Before call:', args),
  after: (result) => console.log('After call:', result)
})

console.log(proxiedFn(2, 3)) // Before call: [2, 3], After call: 5, 5

Syntax

typescript
function proxied<T extends (...args: any[]) => any>(
  fn: T,
  handlers: {
    before?: (args: Parameters<T>) => void | Parameters<T>
    after?: (result: ReturnType<T>) => void | ReturnType<T>
    error?: (error: any) => void | ReturnType<T>
  }
): T

Parameters

  • fn (function): The original function to proxy
  • handlers (object): Proxy handlers
    • before (function): Called before the original function, can modify arguments
    • after (function): Called after the original function, can modify return value
    • error (function): Called when the original function throws an error

Return Value

Returns a proxied function with the same signature as the original function.

Examples

Basic Function Proxy

typescript
import { proxied } from 'radash'

const add = (a: number, b: number) => a + b
const loggedAdd = proxied(add, {
  before: (args) => console.log('Adding:', args),
  after: (result) => console.log('Result:', result)
})

console.log(loggedAdd(5, 3)) // Adding: [5, 3], Result: 8, 8

Parameter Modification

typescript
import { proxied } from 'radash'

const multiply = (a: number, b: number) => a * b
const validatedMultiply = proxied(multiply, {
  before: (args) => {
    const [a, b] = args
    if (a < 0 || b < 0) {
      throw new Error('Negative numbers not allowed')
    }
    return args
  }
})

console.log(validatedMultiply(2, 3)) // 6
// validatedMultiply(-1, 3) // Error: Negative numbers not allowed

Return Value Modification

typescript
import { proxied } from 'radash'

const getUser = (id: number) => ({ id, name: `User ${id}` })
const cachedGetUser = proxied(getUser, {
  after: (result) => {
    console.log('Caching result:', result)
    return { ...result, cached: true }
  }
})

console.log(cachedGetUser(1)) // Caching result: { id: 1, name: 'User 1' }, { id: 1, name: 'User 1', cached: true }

Error Handling

typescript
import { proxied } from 'radash'

const divide = (a: number, b: number) => {
  if (b === 0) throw new Error('Division by zero')
  return a / b
}

const safeDivide = proxied(divide, {
  error: (error) => {
    console.error('Division error:', error.message)
    return 0
  }
})

console.log(safeDivide(10, 2)) // 5
console.log(safeDivide(10, 0)) // Division error: Division by zero, 0

Async Function Proxy

typescript
import { proxied } from 'radash'

const fetchUser = async (id: number) => {
  const response = await fetch(`https://api.example.com/users/${id}`)
  return response.json()
}

const loggedFetchUser = proxied(fetchUser, {
  before: (args) => console.log('Fetching user:', args[0]),
  after: (result) => console.log('User fetched:', result),
  error: (error) => {
    console.error('Fetch error:', error.message)
    return { error: 'User not found' }
  }
})

const user = await loggedFetchUser(1)

Performance Monitoring

typescript
import { proxied } from 'radash'

const expensiveOperation = (data: number[]) => {
  return data.reduce((sum, num) => sum + num, 0)
}

const monitoredOperation = proxied(expensiveOperation, {
  before: (args) => {
    console.time('operation')
    return args
  },
  after: (result) => {
    console.timeEnd('operation')
    return result
  }
})

console.log(monitoredOperation([1, 2, 3, 4, 5])) // operation: 0.123ms, 15

Validation Proxy

typescript
import { proxied } from 'radash'

const createUser = (name: string, age: number, email: string) => ({
  id: Date.now(),
  name,
  age,
  email
})

const validatedCreateUser = proxied(createUser, {
  before: (args) => {
    const [name, age, email] = args
    
    if (!name || name.length < 2) {
      throw new Error('Name must be at least 2 characters')
    }
    
    if (age < 0 || age > 150) {
      throw new Error('Age must be between 0 and 150')
    }
    
    if (!email.includes('@')) {
      throw new Error('Invalid email format')
    }
    
    return args
  }
})

try {
  const user = validatedCreateUser('Alice', 25, 'alice@example.com')
  console.log('User created:', user)
} catch (error) {
  console.error('Validation failed:', error.message)
}

Caching Proxy

typescript
import { proxied } from 'radash'

const cache = new Map()

const expensiveCalculation = (x: number, y: number) => {
  // Simulate expensive operation
  return Math.pow(x, y) + Math.sqrt(x + y)
}

const cachedCalculation = proxied(expensiveCalculation, {
  before: (args) => {
    const key = JSON.stringify(args)
    if (cache.has(key)) {
      console.log('Cache hit for:', args)
      return { cached: true, result: cache.get(key) }
    }
    return args
  },
  after: (result) => {
    const key = JSON.stringify(arguments)
    cache.set(key, result)
    console.log('Cached result for:', arguments)
    return result
  }
})

console.log(cachedCalculation(2, 3)) // 8.236, Cached result for: [2, 3]
console.log(cachedCalculation(2, 3)) // Cache hit for: [2, 3], 8.236

Logging Proxy

typescript
import { proxied } from 'radash'

const processData = (data: any[]) => {
  return data.filter(item => item > 0).map(item => item * 2)
}

const loggedProcessData = proxied(processData, {
  before: (args) => {
    console.log('Processing data:', args[0])
    return args
  },
  after: (result) => {
    console.log('Processed result:', result)
    return result
  },
  error: (error) => {
    console.error('Processing error:', error.message)
    return []
  }
})

console.log(loggedProcessData([1, -2, 3, 0, 5]))
// Processing data: [1, -2, 3, 0, 5]
// Processed result: [2, 6, 10]
// [2, 6, 10]

Rate Limiting Proxy

typescript
import { proxied } from 'radash'

const callCounts = new Map()

const apiCall = async (endpoint: string) => {
  const response = await fetch(endpoint)
  return response.json()
}

const rateLimitedApiCall = proxied(apiCall, {
  before: (args) => {
    const endpoint = args[0]
    const count = callCounts.get(endpoint) || 0
    
    if (count >= 10) {
      throw new Error(`Rate limit exceeded for ${endpoint}`)
    }
    
    callCounts.set(endpoint, count + 1)
    return args
  },
  error: (error) => {
    console.error('API call failed:', error.message)
    return { error: error.message }
  }
})

// Usage
try {
  const data = await rateLimitedApiCall('https://api.example.com/data')
  console.log('API response:', data)
} catch (error) {
  console.error('Rate limit error:', error.message)
}

Authentication Proxy

typescript
import { proxied } from 'radash'

const protectedResource = (userId: number, action: string) => {
  return `User ${userId} performed ${action}`
}

const authenticatedResource = proxied(protectedResource, {
  before: (args) => {
    const [userId, action] = args
    
    // Simulate authentication check
    if (!userId || userId <= 0) {
      throw new Error('Unauthorized: Invalid user ID')
    }
    
    if (!action || action.length === 0) {
      throw new Error('Invalid action')
    }
    
    return args
  },
  error: (error) => {
    console.error('Authentication error:', error.message)
    return { error: error.message, authorized: false }
  }
})

console.log(authenticatedResource(1, 'read')) // User 1 performed read
console.log(authenticatedResource(0, 'write')) // Authentication error: Unauthorized: Invalid user ID, { error: 'Unauthorized: Invalid user ID', authorized: false }

Transformation Proxy

typescript
import { proxied } from 'radash'

const formatText = (text: string, format: string) => {
  switch (format) {
    case 'uppercase': return text.toUpperCase()
    case 'lowercase': return text.toLowerCase()
    case 'titlecase': return text.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())
    default: return text
  }
}

const enhancedFormatText = proxied(formatText, {
  before: (args) => {
    let [text, format] = args
    
    // Normalize text
    text = text.trim()
    
    // Validate format
    if (!['uppercase', 'lowercase', 'titlecase'].includes(format)) {
      format = 'titlecase' // Default format
    }
    
    return [text, format]
  },
  after: (result) => {
    // Add formatting info
    return {
      original: arguments[0],
      formatted: result,
      format: arguments[1]
    }
  }
})

console.log(enhancedFormatText('  hello world  ', 'uppercase'))
// { original: '  hello world  ', formatted: 'HELLO WORLD', format: 'uppercase' }

Notes

  1. Interception: Can intercept function calls before and after execution
  2. Modification: Can modify arguments and return values
  3. Error handling: Can catch and handle errors gracefully
  4. Performance: Minimal overhead for simple proxies
  5. Composition: Can be composed with other functional utilities

Differences from Other Methods

  • proxied: Creates function proxies with interception capabilities
  • memo: Caches function results
  • debounce: Delays function execution
  • throttle: Limits function execution frequency

Performance

  • Time Complexity: O(1) for proxy creation
  • Memory: Minimal overhead for function wrapping
  • Use Cases: Logging, validation, caching, monitoring, authentication

Released under the MIT License.