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