I've got the following code that I'm using to track async request statuses. It uses _type
as a discriminator, as well as status
.
In the following code I define two AsyncStatus types: LoginAsyncStatus
and SearchAsyncStatus
. They differ by _type
and by the success
value
.
The problems is that TypeScript seems to be incorrectly narrowing the type of the discriminated union.
export type AsyncStatus<BrandT extends string, T = undefined> =
| { id: string; _type: BrandT; error?: never; state: "loading"; value?: never; }
| { id: string; _type: BrandT; error: Error; state: "error"; value?: never }
| { id: string; _type: BrandT; error?: never; state: "success"; value: T };
export type ExtractAsyncStatusByType<
TName extends ApiAsyncStatus["_type"],
TType
> = TType extends AsyncStatus<TName, any> ? TType : never;
export type LoginAsyncStatus = AsyncStatus<"LOGIN", { refreshToken: string }>;
export type SearchAsyncStatus = AsyncStatus<"SEARCH", string[]>;
export type ApiAsyncStatus = LoginAsyncStatus | SearchAsyncStatus;
export type Registry = Partial<Record<ApiAsyncStatus["id"], ApiAsyncStatus>>;
export const getApiAsyncStatus = <T extends ApiAsyncStatus["_type"]>(
registry: Registry,
id: string,
type: T,
): ExtractAsyncStatusByType<T, ApiAsyncStatus> | undefined => {
let status = registry[id];
if (status !== undefined && status._type !== type) {
/**
* Property 'value' is missing in type
* '{ _type: T; error: Error; id: string; state: "error"; }'
* but required in type
* '{ id: string; _type: "SEARCH"; error?: undefined; state: "success"; value: string[]; }'
* .ts(2322)
*/
status = {
_type: type,
error: new Error(`Expected _type ${type}, but received ${status._type}`),
id,
state: "error",
}; // err
}
return status as ExtractAsyncStatusByType<T, ApiAsyncStatus> | undefined;
};
I've updated the initial question where the question was about returning the appropriate type in the case where I wasn't trying to dynamically create a status.