Skip to content

Instantly share code, notes, and snippets.

@felipeblassioli
Created June 23, 2024 17:48
Show Gist options
  • Save felipeblassioli/4bfbbb49f57b7f2d474217108c0832c2 to your computer and use it in GitHub Desktop.
Save felipeblassioli/4bfbbb49f57b7f2d474217108c0832c2 to your computer and use it in GitHub Desktop.
etude-ts-default-options

Merging options and defaults

  1. Use the spread operator with type inference:
export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
  fn: T,
  options: Partial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
  const mergedOptions: ICacheOptions<KeyType, ResultType, ErrorType> = {
    ...DEFAULT_CACHE_OPTIONS,
    ...options
  };

  // Rest of the function implementation...
}

This approach uses the spread operator to merge the default options with the provided options. The Partial<> utility type allows for partial options to be passed.

  1. Use a utility function for deep merging with type safety:
import { defaults } from 'options-defaults';

export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
  fn: T,
  options: Partial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
  const mergedOptions = defaults(DEFAULT_CACHE_OPTIONS, options);

  // Rest of the function implementation...
}

This approach uses the options-defaults library, which provides a type-safe way to merge options deeply[1]. It handles nested objects and arrays correctly while maintaining type information.

  1. Use TypeScript's built-in utility types:
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
  fn: T,
  options: DeepPartial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
  const mergedOptions: ICacheOptions<KeyType, ResultType, ErrorType> = {
    ...DEFAULT_CACHE_OPTIONS,
    ...options as ICacheOptions<KeyType, ResultType, ErrorType>
  };

  // Rest of the function implementation...
}

This approach uses a custom DeepPartial type to allow for partial options at any depth. It provides more flexibility for nested options.

  1. Use TypeScript 4.0+'s template literal types for more precise option merging:
type MergeOptions<T, U> = {
  [K in keyof T | keyof U]: K extends keyof U ? U[K] : K extends keyof T ? T[K] : never;
};

export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
  fn: T,
  options: Partial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
  const mergedOptions: MergeOptions<typeof DEFAULT_CACHE_OPTIONS, typeof options> = {
    ...DEFAULT_CACHE_OPTIONS,
    ...options
  };

  // Rest of the function implementation...
}

This approach uses advanced type manipulation to create a merged type that accurately represents the combination of default and provided options[3].

These approaches are more idiomatic in TypeScript and provide better type safety and inference compared to using Object.assign. They allow for partial options to be passed while ensuring that all required properties are present in the final merged options object.

Citations: [1] https://github.com/radarsu/options-defaults [2] https://stackoverflow.com/questions/9602449/a-javascript-design-pattern-for-options-with-default-values [3] https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html [4] https://www.typescriptlang.org/docs/handbook/advanced-types.html [5] protobufjs/protobuf.js#1572

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment