Given I have a type Validated
with the following form:
type Valid<T> = { isValid: true, value: T };
type Invalid = { isValid: false };
type Validated<T> = Valid<T> | Invalid;
const valid = <T>(value: T): Valid => ({ isValid: true, value });
const Invalid : Invalid = { isValid: false };
How would I go about typing the following function so that the output represents a tuple in the same form as the arguments list?
const zip = (...items) =>
items.all(item => item.isValid)
? { isValid: true, value: items.map(item => item.value) }
: { isValid: false };
Examples of what I want the type system to produce:
const result = zip(valid(1), valid("a"), valid("b"), valid(true));
// result is marked as Valid<[number, string, string, boolean]> | Invalid;
If the form of the function I described does not allow for this, then is there a form that does?
I tried the following form:
const allValid = <T>(items: Validated<T>[]): items is Valid<T>[] =>
items.all(item => item.isValid);
const zip = <T>(items: Validated<T>[]): Valid<T[]> | Invalid =>
allValid(items) ? valid(items.map(item => item.value)) : Invalid;
However this erases the positional-preserving type of the tuple and outputs something more akin to
Valid<[number | string | boolean]> | Invalid
for the example from above.
1
After playing around for a while I found a solution that worked.
export type Valid<T> = { isValid: true; value: T };
export type Invalid = { isValid: false };
export type Validated<T> = { isValid: true; value: T } | { isValid: false };
export const valid = <T>(value: T): Valid<T> => ({
isValid: true,
value,
});
export const Invalid: Readonly<Invalid> = { isValid: false };
/** Used for type predicate purposes */
export const isValid = <T>(item: Validated<T>): item is Valid<T> =>
item.isValid;
/** Used for type predicate purposes */
export const isInvalid = <T>(item: Validated<T>): item is Invalid =>
!item.isValid;
export const allValid = <T>(items: Validated<T>[]) => items.every(isValid);
/** Converts a tuple type to a tuple of validated objects, presering the order */
type ValidatedObjs<Values extends {}[]> = {
[Key in keyof Values]: Validated<Values[Key]>;
};
/**
* Combines multiple `Validated<>` objects together.
* The result is `Valid<>` iff all items in the array are `Valid<>`
*/
export const zip = <Values extends {}[]>(
items: ValidatedObjs<Values>
): Validated<Values> =>
allValid(items)
? (valid(items.map((item: any) => item.value)) as any)
: Invalid;
It turned out the solution was to use the tuple type as the generic argument to zip
and then translate that type to a list of Validated<>
objects in the argument type for items
. That way I could access the original tuple type, and the have the typing for the list of Validated<>
objects.