Skip to content

Instantly share code, notes, and snippets.

@sidola
Last active January 4, 2022 09:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sidola/26b946557262f8f9323fad33d0c068a1 to your computer and use it in GitHub Desktop.
Save sidola/26b946557262f8f9323fad33d0c068a1 to your computer and use it in GitHub Desktop.
Make function properties optional
/*
Why are we using Exclude<..., undefined>? Because when extracting
keys, optional properties will resolve to undefined. See here:
https://github.com/microsoft/TypeScript/issues/34992#issuecomment-551822423
---
The Optional/Required types below are leveraging the fact that:
{ a?: string, b: string } extends { a: string } => false
{ a?: string, b: string } extends { b: string } => true
Meaning, for each key:type pair, we create a Record type, then
check if the wider type will extend it. Since we can never extend
a required property into an optional, we have a way to detect
optional properties.
*/
type OptionalKeys<T> = Exclude<{ [K in keyof T]: T extends Record<K, T[K]> ? never : K }[keyof T], undefined>
type RequiredKeys<T> = Exclude<{ [K in keyof T]: T extends Record<K, T[K]> ? K : never }[keyof T], undefined>
type FunctionKeys<T> = Exclude<{ [K in keyof T]: T[K] extends Function ? K : never }[keyof T], undefined>
type NonFunctionKeys<T> = Exclude<{ [K in keyof T]: T[K] extends Function ? never : K }[keyof T], undefined>
export type FunctionsPartial<Type> =
// These types we just handle as-is, and send their original type
// back out.
Type extends Function | any[] | string | number | boolean | undefined
? Type
// When we encounter objects, we will create a new type, where we
// move all function properties to optional keys. Notice that we
// also have to deal with all previously optional keys, otherwise
// we'd make everything EXCEPT functions required.
: Type extends object
? (
{ [Key in OptionalKeys<Type> | FunctionKeys<Type>]+?: FunctionsPartial<Type[Key]> } &
{ [Key in RequiredKeys<Type> & NonFunctionKeys<Type>]-?: FunctionsPartial<Type[Key]> }
)
// Reaching this point is a no-op, as all types should've been exhausted above.
: "¤function-partials-is-broken"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment