type guard function is not narrowing the type in array filter

113 Views Asked by At

I have a very simple scenario where I have some response from my DB and I want to narrow it to certain type. The response type is a superset of the expected type, so filtering has a runtime utility is not only for type safety. At first I tried to directly use the utility is from valibot, and it didn't worked, so I created a dedicated type guard function. Here is a minimal reproducible example:

declare function getChatMessages(
  chat_id: string,
  limit?: number,
): Promise<
  {
    whatId: string;
    chat_id: string;
    from: string;
    fromMe: boolean;
    body: string | null;
    timestamp: number;
    type: string;
    isForwarded: boolean | null;
    id: number;
    data: unknown;
  }[]
>;
import { string, object, optional, boolean, enumType, Output, toTrimmed, merge, number, is } from 'valibot'

const messageTypesToAccount = enumType(['chat','ptt']);

export const messageCore = object({
  whatId: string(),
  chat_id: string(),
  from: string(),
  fromMe: boolean(),
  body: optional(string([toTrimmed()])),
  timestamp: number(),
  // Image types may contain a body text, that's why they are included
  type: messageTypesToAccount,
  isForwarded: optional(boolean(), false),
  hasQuotedMsg: optional(boolean(), false),
  hasReaction: optional(boolean(), false),
  hasMedia: optional(boolean(), false),
})

export const textMessage = merge([messageCore, object({
  body: string(),
})])
export type TextMessage = Output<typeof textMessage>

export function isTextMessage(msg: unknown): msg is TextMessage {
  if (is(textMessage,msg)) {
    return true;
  }
  return false;
}

const messages = await getChatMessages("99");
const validMessages: TextMessage[] = messages.filter(isTextMessage);

Typescript complains that the body property of the DB response (string|null) is not assignable to the target type body property that is just string, but that is the whole point of the filter and predicate function.

And here is a playground link that you can test.

This is the typescript error I get in the playground:

Type '{ whatId: string; chat_id: string; from: string; fromMe: boolean; body: string | null; timestamp: number; type: string; isForwarded: boolean | null; id: number; data: unknown; }[]' is not assignable to type '{ whatId: string; chat_id: string; from: string; fromMe: boolean; body: string; timestamp: number; type: "chat" | "ptt"; isForwarded: boolean; hasQuotedMsg: boolean; hasReaction: boolean; hasMedia: boolean; }[]'.
  Type '{ whatId: string; chat_id: string; from: string; fromMe: boolean; body: string | null; timestamp: number; type: string; isForwarded: boolean | null; id: number; data: unknown; }' is missing the following properties from type '{ whatId: string; chat_id: string; from: string; fromMe: boolean; body: string; timestamp: number; type: "chat" | "ptt"; isForwarded: boolean; hasQuotedMsg: boolean; hasReaction: boolean; hasMedia: boolean; }': hasQuotedMsg, hasReaction, hasMedia

My guess is that, because the types have no overlap typescript infers that it is impossible to narrow it, bu in theory that will just mean that I will get an empty array

0

There are 0 best solutions below