Functions and utilities should operate at appropriate abstraction levels without being aware of higher-level concepts or implementation details. This prevents tight coupling and improves reusability and testability.

When designing APIs, ensure that:

Example: Instead of making a utility aware of a specific Client type:

// ❌ Bad - utility knows about high-level Client concept
export function createCompositeProxy<T extends object>(client: Client, target: T, layers: CompositeProxyLayer[]): T {
  // ...
  getPrototypeOf: () => Object.getPrototypeOf(client._originalClient),
}

Use an abstract parameter that provides only what’s needed:

// ✅ Good - utility works with abstract prototype provider
export function createCompositeProxy<T extends object>(prototypeProvider: {}, target: T, layers: CompositeProxyLayer[]): T {
  // ...
  getPrototypeOf: () => Object.getPrototypeOf(prototypeProvider),
}

Similarly, abstract scattered conditionals into proper interfaces:

// ❌ Bad - conditionals scattered throughout class
if (TARGET_BUILD_TYPE === 'rn') {
  // React Native specific logic
}

// ✅ Good - abstracted into library interface
const library = await libraryLoader.load()
await library.startTransaction(options)

This approach makes code more modular, testable, and maintainable by ensuring each component operates at its appropriate level of abstraction.