7

I'm trying to build a dynamic type for a builder

export type Builder<T, K extends keyof T> = {
    [P in K]: (value: T[P]) => Builder<T, K>;
} & {
    build(): Readonly<T>;
};

If I have a class or an interface with optional properties, I get this kind of error:

$ tsc
test/OtherClass.ts:29:1 - error TS2722: Cannot invoke an object which is possibly 'undefined'.

29 OtherClass.builder().patate(Patate.AU_FOUR).build();
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~

This is my class

export class OtherClass {
    constructor(
        public literal: string | undefined,
        public patate?: Patate,
        public hello: Readonly<Hello> = {},
    ) { }

    static builder: () => Builder<OtherClass, keyof OtherClass> = builder.for(OtherClass);
}

I was expecting the type to create a builder with a non-optional method for each properties, but for some reason, the optionality of patate seem inherent to the key and not the type. I don't get this behavior with the property literal

It looks like an issue to me. I'm using typescript 3.1.4. Is there another way to remove the question mark dynamically?

I've tried to use the NonNullable helper to create first a copy of my type with nothing nullable, but patate remains optional.

This is the effective type that vscode gives me

(property) patate?: ((value: Patate | undefined) => Builder<OtherClass, "literal" | "patate" | "hello">) | undefined
Francis
  • 3,335
  • 20
  • 46

1 Answers1

12

Does this help?

export type Builder<T, K extends keyof T> = {
    [P in K]-?: (value: T[P]) => Builder<T, K>;
} & {
    build(): Readonly<T>;
};

Note the -? which does what you want - removes optionality.

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • Yes thanks, but do you know why it inherits the optionality from the key name? – Francis Nov 15 '18 at 14:00
  • 3
    When the "constraint" of a mapped type (the type after `in`, which determines the set of properties) is of the form `keyof T` or is a type parameter constrained by `keyof T` for some type `T`, the mapped type is "homomorphic" and copies modifiers (optional and readonly) from `T`. There's a bit about this [here](https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types). – Matt McCutchen Nov 15 '18 at 14:09