cfg() - A Flexible Configuration Value Handler in TypeScript

cfg() - A Flexible Configuration Value Handler in TypeScript

8 min read
typescript payload-cms feature-flags

In Payload CMS, everything revolves around your configuration setup. I've been working on several Payload plugins and have found myself in need of a utility function that helps me retrieve configuration values while falling back to a provided default value. Specifically, I'm checking for the presence of new custom properties on my Payload CollectionConfig.

In this article, I'll outline a versatile TypeScript utility function called cfg() that simplifies the process of retrieving configuration values while providing type safety and flexible fallback options.

Introduction

The cfg() utility function is designed to safely extract configuration values from objects while handling undefined cases and providing default values. What makes this function particularly useful is its ability to handle boolean conversions and its type-safe implementation using TypeScript's powerful type system.

Breaking Down the Function

Let's examine the key components of this utility:

Type Safety with Generics

export function cfg<T>(
    obj: T | undefined | true,
    prop: keyof T,
    defaultValue: unknown = true)

The function uses TypeScript generics (<T>) to ensure type safety. The first parameter obj can be of type T, undefined, or true, making it extremely flexible. The prop parameter must be a valid key of type T, preventing accidental access to non-existent properties.

Logical Flow

The function follows a clear decision tree:

  1. First, it checks if the input object is explicitly true:
if (obj === true) {
    return true;
}
  1. Then, it attempts to retrieve the specified property using optional chaining:
const value = obj?.[prop];
  1. The value is evaluated based on its type:
    • Boolean values are returned directly
    • Undefined values trigger the default value
    • Other values are converted to boolean

Real-World Applications

This utility is particularly useful in scenarios such as:

Feature Flags

interface FeatureFlags {
    darkMode: boolean;
    betaFeatures: boolean;
}

const flags = {
    darkMode: true,
    betaFeatures: undefined
};

const isDarkMode = cfg(flags, 'darkMode');        // true
const hasBetaFeatures = cfg(flags, 'betaFeatures'); // true (default)

Configuration Management

interface AppConfig {
    isDebugMode: boolean;
    enableAnalytics: number; // 0 or 1
}

const config = {
    isDebugMode: true,
    enableAnalytics: 1
};

const analyticsEnabled = cfg(config, 'enableAnalytics'); // true

TypeScript Benefits

The implementation leverages several TypeScript features that make the utility more robust:

  1. Generic Types: The <T> parameter allows the function to work with any object type while maintaining type safety.
  2. Keyof Operator: Using keyof T ensures that only valid property names can be passed.
  3. Union Types: The T | undefined | true type allows for flexible input handling.
  4. Optional Chaining: The ?. operator safely handles potential undefined values.

Considerations and Limitations

While the utility is powerful, there are some things to keep in mind:

  1. The function always returns a boolean value, so it's not suitable for retrieving non-boolean configuration values.
  2. The default value parameter is typed as unknown, which might be too permissive in some cases.
  3. The automatic boolean conversion might not always be desirable for non-boolean input values.

Conclusion

The cfg() utility function demonstrates how TypeScript's type system can be leveraged to create safe and flexible utilities for handling configuration values. Its ability to handle undefined cases, provide default values, and ensure type safety makes it a valuable tool for managing feature flags and configuration settings in TypeScript applications.

Whether you're building a complex application with multiple feature flags or simply need a reliable way to handle boolean configuration values, this utility provides a robust solution while maintaining code quality through strong typing.

Consider incorporating this utility into your TypeScript projects when you need a reliable way to handle configuration values with built-in type safety and flexible fallback options.