TypeScript type restrictions sometimes not applied

Viewed 39

I'm designing an authentication backend with TypeScript. I have the following types to describe a user or a guest:

export type LoadingUser = {
  loading: true;
};

export type AuthenticatedUser = {
  loading: false;
  error: string | null;
  loggedIn: true;
  name: string;
};

export type GuestUser = {
  loading: false;
  error: string | null;
  loggedIn: false;
};

LoadingUser is used to indicate when the user is still being fetched, the frontend could show a spinner here. Notice it's either only loading: true with no other properties, or loading: false with other properties, and then either loading: false; loggedIn: false; error: string | null with no other properties, or loading: false; loggedIn: true; error: string | null with other properties.

I'm then using these types to define my User type:

export type User =
  | LoadingUser
  | AuthenticatedUser
  | GuestUser;

I want to use this to type-check all my API responses, but I have come across the following problem

const user: User = {
  loading: true,
  loggedIn: true, // Problem: this property is allowed on this object even though `loading: true`
};

When I'm just using AuthenticatedUser and LoadingUser together, or GuestUser with LoadingUser, this problem does not happen. TypeScript then correctly errors and tells me loggedIn does not exist on type LoadingUser like I want.

Why does using type User make TypeScript not report this error?

In advance: Thank you so much for helping me, I've been struggling with this for days!

1 Answers
export type LoadingUser = {
    loading: true;
    loggedIn?: never;
};

The problem is that assigning { loading: true, loggedIn: true } to LoadingUser is correct (and actually should not be an error) by the types theory - the object conforms to the LoadingUser type constraint. Typescript's restriction Object literal may only specify known properties is an extra helper because in practice specifying unknown properties in object literal is usually a mistake (or bad type design) althrough it is theoretically OK, but this helper can definitely not catch all possible cases.

However by declaring loggedIn?: never you explicitly disallow LoadingUser to have this attribute, then your "bad" object will not conform to this interface and report and error.

Related