Inspired by neverthrow and this nice answer I'm experimenting with a "flat result API". The target is to write code like this with full type-safety:
const res =
Math.random() < 0.5
? ok({ id: 42 })
: err({ message: "something went wrong" });
if (res.isOk()) {
console.log("Ok:", res.id);
} else {
console.log("Error:", res.message);
}
// Side note: The difference to neverthrow is that value/error aren't nested, i.e.,
// no unwrapping is needed at the cost of only supporting objects.
I have achieved that with the following implementation. However it required an unexpected "hack":
export type Result<T, E> = Ok<T> | Err<E>;
type Ok<T> = OkTag & T;
type Err<E> = ErrTag & E;
class OkTag {
private _isOk = true; // Why do I need this?
constructor(value: Record<any, any>) {
Object.assign(this, value);
}
isOk(): this is OkTag {
return true;
}
isErr(): this is ErrTag {
return false;
}
}
class ErrTag {
private _isOk = false; // Why do I need this?
constructor(error: Record<any, any>) {
Object.assign(this, error);
}
isOk(): this is OkTag {
return false;
}
isErr(): this is ErrTag {
return true;
}
}
export const ok = <T extends Record<any, any>>(value: T): Ok<T> => {
return new OkTag(value) as Ok<T>;
};
export const err = <E extends Record<any, any>>(error: E): Err<E> => {
return new ErrTag(error) as Err<E>;
};
The example above compiles and seems to behave correctly. However if I remove the unused _isOk
property it fails to compile with the errors
In the first branch of the if:
Property 'id' does not exist on type 'Ok<{ id: number; }> | Err<{ message: string; }>'.
Property 'id' does not exist on type 'Err<{ message: string; }>'.
In the second branch of the if:
Property 'message' does not exist on type 'never'.
Apparently the type guards stop working.
I also noticed that the value of the property doesn't matter -- unless I'm making a wrong conclusion the code seems to work with private _dummy = 0
in both classes as well, i.e., the property doesn't have any influence on runtime.
Why is this dummy property needed / is there a way to avoid it?