Search code examples
typescripttypescript-typingsspread-syntaxtypescript-types

TypeScript: How to type object rest spread for any keys in function


I have a function that takes an object and returns an object. It returns the whole incoming object, but adds a key. The shape of the object is unknown, so it could have any keys, but it must have 2 certain keys.

const myFunction = ({
  num1,
  num2,
  ...rest
}: {
  num1: number;
  num2: number;
}) => ({
  num1,
  num2,
  sum: num1 + num2,
  ...rest,
});

myFunction({ num1: 4, num2: 3, foo: 'bar' });
// or myFunction({ num1: 4, num2: 3, baz: 'qux', quux: 'quuz' });

Here TypeScript yells about foo.

Argument of type '{ num1: number; num2: number; foo: string; }' is not assignable to parameter of type '{ num1: number; num2: number; }'.
  Object literal may only specify known properties, and 'foo' does not exist in type '{ num1: number; num2: number; }

That was the simplified example.

Here is my actual function and how I tried to solve it using extends.

import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from 'utils/sessions';

const withAuthentication = async <
  T extends {
    request: NextApiRequest;
    response: NextApiResponse;
  },
  K extends T
>({
  request,
  response,
  ...rest
}: T): Promise<
  {
    userSession: {
      issuer: string;
      publicAddress: string;
      email: string;
    };
  } & K
> => {
  const userSession = await getSession(request);

  return { request, response, userSession, ...rest };
};

export default withAuthentication;

And the actual error is this.

Type '{ request: NextApiRequest; response: NextApiResponse<any>; userSession: any; } & Omit<T, "request" | "response">' is not assignable to type '{ userSession: { issuer: string; publicAddress: string; email: string; }; } & K'.
  Type '{ request: NextApiRequest; response: NextApiResponse<any>; userSession: any; } & Omit<T, "request" | "response">' is not assignable to type 'K'.
    '{ request: NextApiRequest; response: NextApiResponse<any>; userSession: any; } & Omit<T, "request" | "response">' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint '{ request: NextApiRequest; response: NextApiResponse<any>; }'.

How can you type such a function?


Solution

  • Destructuring with the rest parameter makes it tricky to get this to typecheck, but if you just spread the argument object and add the userSession property, you end up with a fairly readable solution:

    const withAuthentication = async <
      T extends {
        request: NextApiRequest;
        response: NextApiResponse;
      }
    >(arg: T): Promise<{
        userSession: {
          issuer: string;
          publicAddress: string;
          email: string;
        };
      } & T> => {
      const userSession = await getSession(arg.request);
      return { ...arg, userSession };
    };
    

    (TypeScript playground)