Cannot spread extra values to instance of extended interface












0















I'm trying to define a data injector (in context: props option of Apollo Client's graphql HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.



I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any for spreading the extra input).



I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.



This is with TypeScript 3.0/3.1 in strict mode.




// data that we want to inject
// 'values' may not be null or undefined
interface InjectedData {
values: string;
}

// generic type of a query's result
interface QueryResult<T> {
// actual data provided by the query
// the injected values will be extracted from the payload
data?: {
error?: string;
loading: boolean;
payload?: [{ id: string; name: string }];
};
// rest of input data that should be propagated as-is
inputData: T;
}


// inject 'values' from a query result and propagate any extra input
// the extra input is explicitly forbidden to contain a 'values' field
type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
const injectValues = <P extends InjectedData>(
{ data, inputData }: QueryResult<Omit<P, InjectedData>>
): P => {
// 'values' is badly written and possibly undefined. TS detects it, which is good
// error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
// inputData are not propagated on yet
const v1: P = {
values: data && data.payload && data.payload.map(i => i.name)
};

// 'values' is fixed, but somehow cannot be assigned to P
// does the extension of OutData require actual extra fields?
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
// inputData are not propagated on yet
const v2: P = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};

// attempt to propagate inputData, but cannot be spread as-is from
// the Pick type
// error TS2698: Spread types may only be created from object types
const v3: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...inputData
};

// type-asserted inputData as object, but then assignment fails again
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
const v4: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};

// type-asserted inputData as any and it compiles...
const v5_1: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// ... but then values is allowed to be undefined again, so possible
// mistakes might not be caught
const v5_2: P = {
values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
...(inputData as any)
};

// without inputData, the inferred type of the RHS is {values: string}
const check1 = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// with inputData type-asserted as object, the inferred type of the RHS is {values: string}
const check2 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// with inputData type-asserted as any, the inferred type of the RHS is any
const check3 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};

// how to achieve both correct checking of 'values' and spreading of inputData?

return { values: [""] };
};












share|improve this question



























    0















    I'm trying to define a data injector (in context: props option of Apollo Client's graphql HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.



    I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any for spreading the extra input).



    I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.



    This is with TypeScript 3.0/3.1 in strict mode.




    // data that we want to inject
    // 'values' may not be null or undefined
    interface InjectedData {
    values: string;
    }

    // generic type of a query's result
    interface QueryResult<T> {
    // actual data provided by the query
    // the injected values will be extracted from the payload
    data?: {
    error?: string;
    loading: boolean;
    payload?: [{ id: string; name: string }];
    };
    // rest of input data that should be propagated as-is
    inputData: T;
    }


    // inject 'values' from a query result and propagate any extra input
    // the extra input is explicitly forbidden to contain a 'values' field
    type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
    const injectValues = <P extends InjectedData>(
    { data, inputData }: QueryResult<Omit<P, InjectedData>>
    ): P => {
    // 'values' is badly written and possibly undefined. TS detects it, which is good
    // error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
    // inputData are not propagated on yet
    const v1: P = {
    values: data && data.payload && data.payload.map(i => i.name)
    };

    // 'values' is fixed, but somehow cannot be assigned to P
    // does the extension of OutData require actual extra fields?
    // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
    // inputData are not propagated on yet
    const v2: P = {
    values: (data && data.payload && data.payload.map(i => i.name)) ||
    };

    // attempt to propagate inputData, but cannot be spread as-is from
    // the Pick type
    // error TS2698: Spread types may only be created from object types
    const v3: P = {
    values: (data && data.payload && data.payload.map(i => i.name)) || ,
    ...inputData
    };

    // type-asserted inputData as object, but then assignment fails again
    // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
    const v4: P = {
    values: (data && data.payload && data.payload.map(i => i.name)) || ,
    ...(inputData as object)
    };

    // type-asserted inputData as any and it compiles...
    const v5_1: P = {
    values: (data && data.payload && data.payload.map(i => i.name)) || ,
    ...(inputData as any)
    };
    // ... but then values is allowed to be undefined again, so possible
    // mistakes might not be caught
    const v5_2: P = {
    values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
    ...(inputData as any)
    };

    // without inputData, the inferred type of the RHS is {values: string}
    const check1 = {
    values: (data && data.payload && data.payload.map(i => i.name)) ||
    };
    // with inputData type-asserted as object, the inferred type of the RHS is {values: string}
    const check2 = {
    values: (data && data.payload && data.payload.map(i => i.name)) || ,
    ...(inputData as object)
    };
    // with inputData type-asserted as any, the inferred type of the RHS is any
    const check3 = {
    values: (data && data.payload && data.payload.map(i => i.name)) || ,
    ...(inputData as any)
    };

    // how to achieve both correct checking of 'values' and spreading of inputData?

    return { values: [""] };
    };












    share|improve this question

























      0












      0








      0








      I'm trying to define a data injector (in context: props option of Apollo Client's graphql HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.



      I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any for spreading the extra input).



      I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.



      This is with TypeScript 3.0/3.1 in strict mode.




      // data that we want to inject
      // 'values' may not be null or undefined
      interface InjectedData {
      values: string;
      }

      // generic type of a query's result
      interface QueryResult<T> {
      // actual data provided by the query
      // the injected values will be extracted from the payload
      data?: {
      error?: string;
      loading: boolean;
      payload?: [{ id: string; name: string }];
      };
      // rest of input data that should be propagated as-is
      inputData: T;
      }


      // inject 'values' from a query result and propagate any extra input
      // the extra input is explicitly forbidden to contain a 'values' field
      type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
      const injectValues = <P extends InjectedData>(
      { data, inputData }: QueryResult<Omit<P, InjectedData>>
      ): P => {
      // 'values' is badly written and possibly undefined. TS detects it, which is good
      // error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
      // inputData are not propagated on yet
      const v1: P = {
      values: data && data.payload && data.payload.map(i => i.name)
      };

      // 'values' is fixed, but somehow cannot be assigned to P
      // does the extension of OutData require actual extra fields?
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      // inputData are not propagated on yet
      const v2: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };

      // attempt to propagate inputData, but cannot be spread as-is from
      // the Pick type
      // error TS2698: Spread types may only be created from object types
      const v3: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...inputData
      };

      // type-asserted inputData as object, but then assignment fails again
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      const v4: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };

      // type-asserted inputData as any and it compiles...
      const v5_1: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };
      // ... but then values is allowed to be undefined again, so possible
      // mistakes might not be caught
      const v5_2: P = {
      values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
      ...(inputData as any)
      };

      // without inputData, the inferred type of the RHS is {values: string}
      const check1 = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };
      // with inputData type-asserted as object, the inferred type of the RHS is {values: string}
      const check2 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };
      // with inputData type-asserted as any, the inferred type of the RHS is any
      const check3 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };

      // how to achieve both correct checking of 'values' and spreading of inputData?

      return { values: [""] };
      };












      share|improve this question














      I'm trying to define a data injector (in context: props option of Apollo Client's graphql HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.



      I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any for spreading the extra input).



      I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.



      This is with TypeScript 3.0/3.1 in strict mode.




      // data that we want to inject
      // 'values' may not be null or undefined
      interface InjectedData {
      values: string;
      }

      // generic type of a query's result
      interface QueryResult<T> {
      // actual data provided by the query
      // the injected values will be extracted from the payload
      data?: {
      error?: string;
      loading: boolean;
      payload?: [{ id: string; name: string }];
      };
      // rest of input data that should be propagated as-is
      inputData: T;
      }


      // inject 'values' from a query result and propagate any extra input
      // the extra input is explicitly forbidden to contain a 'values' field
      type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
      const injectValues = <P extends InjectedData>(
      { data, inputData }: QueryResult<Omit<P, InjectedData>>
      ): P => {
      // 'values' is badly written and possibly undefined. TS detects it, which is good
      // error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
      // inputData are not propagated on yet
      const v1: P = {
      values: data && data.payload && data.payload.map(i => i.name)
      };

      // 'values' is fixed, but somehow cannot be assigned to P
      // does the extension of OutData require actual extra fields?
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      // inputData are not propagated on yet
      const v2: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };

      // attempt to propagate inputData, but cannot be spread as-is from
      // the Pick type
      // error TS2698: Spread types may only be created from object types
      const v3: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...inputData
      };

      // type-asserted inputData as object, but then assignment fails again
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      const v4: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };

      // type-asserted inputData as any and it compiles...
      const v5_1: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };
      // ... but then values is allowed to be undefined again, so possible
      // mistakes might not be caught
      const v5_2: P = {
      values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
      ...(inputData as any)
      };

      // without inputData, the inferred type of the RHS is {values: string}
      const check1 = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };
      // with inputData type-asserted as object, the inferred type of the RHS is {values: string}
      const check2 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };
      // with inputData type-asserted as any, the inferred type of the RHS is any
      const check3 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };

      // how to achieve both correct checking of 'values' and spreading of inputData?

      return { values: [""] };
      };








      // data that we want to inject
      // 'values' may not be null or undefined
      interface InjectedData {
      values: string;
      }

      // generic type of a query's result
      interface QueryResult<T> {
      // actual data provided by the query
      // the injected values will be extracted from the payload
      data?: {
      error?: string;
      loading: boolean;
      payload?: [{ id: string; name: string }];
      };
      // rest of input data that should be propagated as-is
      inputData: T;
      }


      // inject 'values' from a query result and propagate any extra input
      // the extra input is explicitly forbidden to contain a 'values' field
      type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
      const injectValues = <P extends InjectedData>(
      { data, inputData }: QueryResult<Omit<P, InjectedData>>
      ): P => {
      // 'values' is badly written and possibly undefined. TS detects it, which is good
      // error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
      // inputData are not propagated on yet
      const v1: P = {
      values: data && data.payload && data.payload.map(i => i.name)
      };

      // 'values' is fixed, but somehow cannot be assigned to P
      // does the extension of OutData require actual extra fields?
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      // inputData are not propagated on yet
      const v2: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };

      // attempt to propagate inputData, but cannot be spread as-is from
      // the Pick type
      // error TS2698: Spread types may only be created from object types
      const v3: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...inputData
      };

      // type-asserted inputData as object, but then assignment fails again
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      const v4: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };

      // type-asserted inputData as any and it compiles...
      const v5_1: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };
      // ... but then values is allowed to be undefined again, so possible
      // mistakes might not be caught
      const v5_2: P = {
      values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
      ...(inputData as any)
      };

      // without inputData, the inferred type of the RHS is {values: string}
      const check1 = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };
      // with inputData type-asserted as object, the inferred type of the RHS is {values: string}
      const check2 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };
      // with inputData type-asserted as any, the inferred type of the RHS is any
      const check3 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };

      // how to achieve both correct checking of 'values' and spreading of inputData?

      return { values: [""] };
      };





      // data that we want to inject
      // 'values' may not be null or undefined
      interface InjectedData {
      values: string;
      }

      // generic type of a query's result
      interface QueryResult<T> {
      // actual data provided by the query
      // the injected values will be extracted from the payload
      data?: {
      error?: string;
      loading: boolean;
      payload?: [{ id: string; name: string }];
      };
      // rest of input data that should be propagated as-is
      inputData: T;
      }


      // inject 'values' from a query result and propagate any extra input
      // the extra input is explicitly forbidden to contain a 'values' field
      type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
      const injectValues = <P extends InjectedData>(
      { data, inputData }: QueryResult<Omit<P, InjectedData>>
      ): P => {
      // 'values' is badly written and possibly undefined. TS detects it, which is good
      // error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
      // inputData are not propagated on yet
      const v1: P = {
      values: data && data.payload && data.payload.map(i => i.name)
      };

      // 'values' is fixed, but somehow cannot be assigned to P
      // does the extension of OutData require actual extra fields?
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      // inputData are not propagated on yet
      const v2: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };

      // attempt to propagate inputData, but cannot be spread as-is from
      // the Pick type
      // error TS2698: Spread types may only be created from object types
      const v3: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...inputData
      };

      // type-asserted inputData as object, but then assignment fails again
      // error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
      const v4: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };

      // type-asserted inputData as any and it compiles...
      const v5_1: P = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };
      // ... but then values is allowed to be undefined again, so possible
      // mistakes might not be caught
      const v5_2: P = {
      values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
      ...(inputData as any)
      };

      // without inputData, the inferred type of the RHS is {values: string}
      const check1 = {
      values: (data && data.payload && data.payload.map(i => i.name)) ||
      };
      // with inputData type-asserted as object, the inferred type of the RHS is {values: string}
      const check2 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as object)
      };
      // with inputData type-asserted as any, the inferred type of the RHS is any
      const check3 = {
      values: (data && data.payload && data.payload.map(i => i.name)) || ,
      ...(inputData as any)
      };

      // how to achieve both correct checking of 'values' and spreading of inputData?

      return { values: [""] };
      };






      typescript apollo-client






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 28 '18 at 9:31









      Matthieu DazyMatthieu Dazy

      13




      13
























          0






          active

          oldest

          votes











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53516194%2fcannot-spread-extra-values-to-instance-of-extended-interface%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53516194%2fcannot-spread-extra-values-to-instance-of-extended-interface%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          A CLEAN and SIMPLE way to add appendices to Table of Contents and bookmarks

          Calculate evaluation metrics using cross_val_predict sklearn

          Insert data from modal to MySQL (multiple modal on website)