Signature

TypeScript signature

function withLatency<
  T extends (...args: any[]) => Promise<any>
>(
  fn: T,
  profile: LatencyPreset | LatencyOptions
): T

The wrapped function has exactly the same type as the original. All parameter types, return type, and overloads are preserved.

Parameters

Function parameters

paramtypedefaultdescription
fn (...args: any[]) => Promise<any> The async function to wrap. Must return a Promise. The wrapped function will have the same signature.
profile LatencyPreset | LatencyOptions Either a preset string name ('postgres', 'redis', etc.) or a custom LatencyOptions object with explicit p50/p99 values.
Options

LatencyOptions

When passing a custom options object instead of a preset string, these fields are available:

fieldtypedefaultdescription
p50 number Median latency in milliseconds. Half of sampled delays will be below this value. Required.
p99 number 99th-percentile latency in milliseconds. Used alongside p50 to fit the lognormal distribution. Must be greater than p50. Required.
errorRate number 0 Fraction of calls that reject with a transient error before the underlying function is called. Range: 0 to 1. A value of 0.01 means 1% of calls fail. Optional.
Return value

What withLatency returns

withLatency returns a new function with the same TypeScript type as fn. Calling the returned function:

  1. Samples a delay from the lognormal distribution parameterized by the profile.
  2. If errorRate fires, immediately rejects with new Error('Simulated transient error').
  3. Otherwise, waits the sampled delay, then calls fn with the original arguments.
  4. Returns fn's resolved value, or rethrows any error thrown by fn.

The returned function is a plain async function — not a Proxy. It can be called, passed around, bound to objects, and destructured exactly like a regular function.

Examples

Code examples

import { withLatency } from 'slowdep'

async function getUser(id: number) {
  return db.query('SELECT * FROM users WHERE id = $1', [id])
}

// Wrap with Postgres preset (p50: 5ms, p99: 200ms, errorRate: 0.001)
const slowGetUser = withLatency(getUser, 'postgres')

// Call exactly like the original
const user = await slowGetUser(42)
console.log(user.name)
import { withLatency } from 'slowdep'

// Custom profile matching your actual production metrics
const slowFetch = withLatency(fetchProduct, {
  p50: 45,    // your measured median
  p99: 600,   // your measured 99th percentile
})

const product = await slowFetch('sku-123')
import { withLatency } from 'slowdep'

// 2% of calls will reject — test your retry logic
const slowCharge = withLatency(createCharge, {
  p50: 200,
  p99: 2000,
  errorRate: 0.02
})

try {
  const charge = await slowCharge({ amount: 1000 })
  fulfill(charge)
} catch (err) {
  // err.message === 'Simulated transient error'
  await scheduleRetry()
}
import { withLatency } from 'slowdep'
import type { LatencyOptions, LatencyPreset } from 'slowdep'

// Type-annotated options object
const opts: LatencyOptions = { p50: 5, p99: 200, errorRate: 0.001 }

// Explicit type parameter when inference needs help
const slowGetUser = withLatency<typeof getUser>(getUser, opts)

// Preset as typed string literal
const preset: LatencyPreset = 'redis'
const slowGet = withLatency(cacheGet, preset)

// Return type is preserved — same as original function
const result: Awaited<ReturnType<typeof getUser>> = await slowGetUser(1)
Behavior

Detailed call behavior

On each invocation of the wrapped function, slowdep follows this sequence:

1
Sample latency
Two uniform random numbers are generated via Math.random(). The Box-Muller transform converts them to a standard normal sample, which is then scaled by the lognormal parameters (mu = ln(p50), sigma derived from p99) and exponentiated. The result is capped at p99 × 3.
2
Check error rate
A third random number is generated and compared to errorRate. If it falls below errorRate, the call rejects immediately with new Error('Simulated transient error'). The original function is not called.
3
Wait the sampled delay
A setTimeout for the sampled duration is awaited. This is the artificial latency added to the call.
4
Call the original function
The original fn is called with all the arguments passed to the wrapper. Its resolved value is returned, or any error it throws is rethrown transparently.