Typescript interface with never typed fields

1k views Asked by At

Given the following interface, what would be a valid assignable value?

interface A {
  x: number,
  y: never
}

What I expected was that const a: A = { x: 1 } would work, but it errors saying field y is required. As soon as I put y in there, it says that the value is not assignable to never.

My actual use case is something like this:

interface Context<T extends MyRequest> {
  id: string;
  token: T extends AuthenticateRequest ? string : never;
}

interface AuthenticatedRequest extends MyRequest { ... }

Here I am unable to create a value for Context<MyRequest> since it says token is missing.

A workaround I currently have is:

type Context<T extends MyRequest> = {
  id: string;
} & (T extends AuthenticatedRequest ? {
  token: string;
} : {})

but for obvious reasons, this looks ugly...

Any ideas how I do this correctly ?

3

There are 3 answers

0
Codi On BEST ANSWER

Based on points mentioned by @Titian Cernicova-Dragomir and @Maciej Sikora, the workaround is the right way to go for the type definition I need. An interface cannot have a field of type never, since it can't be assigned a valid value, thereby making the interface useless.

Here's what I finally used for my code:

type Context<T extends MyRequest> = Readonly<
  {
    logger: MyLogger;
    timestamp?: number;
  } & (T extends AuthenticatedRequest
    ? {
        token: Token;
        user: string;
      }
    : {}) &
    (T extends GameRequest
      ? {
          gameId: string;
        }
      : {}) &
    (T extends ETagRequest
      ? {
          etag: string;
        }
      : {}) &
    (T extends UnParameterizedRequest
      ? {}
      : {
          params: Record<string, string | undefined>;
        })
>;
0
Michael Dera On

You need to set your type to string or undefined. The never type is used when a variable will never be assigned a value, More on that here.

This approach ought to solve your problem I think.

interface A {
  x: number,
  y: string | undefined
}

3
Maciej Sikora On

Type never is representation of empty type. What does it mean - there is no value which belongs to this type, what means you have always compilation error if you use it.

What you need is conditional type at higher level, consider:

type Context<T extends MyRequest> = T extends AuthenticateRequest ? {
  id: string;
  token: string;
} : { id: string };

Such approach remove a burden of setting undefined for token. If token is required, so for T extends AuthenticateRequest it is a string, if it is not, the token field is not there.