- 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.
- 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.
- 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.
- 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