an example for such usecase would be:
type OnlyExtendArr = ???;
const arr1: OnlyExtendArr =
[
{}, //ok
{name: 'name'}, //ok
{name:"name",age: 'age'}, //ok
{age: 'age'} // should error - 'name' is required so this item's type would extend previous type
];
in this example, I want that the type of each element would extend the previous type.
more practically I want to define a type where's the item in the index n
should extend the item in the index n-1
but I don't know how to write it.
is it possible using typescript?
a good start would be extracting the type of the last element in the array, which seems to be not so simple (because you can't access the -1 element in typescript) (solution for this here)
Last element in the array:
type LengthOfTuple<T extends any[]> = T extends { length: infer L } ? L : never;
type DropFirstInTuple<T extends any[]> = ((...args: T) => any) extends (arg: any, ...rest: infer U) => any ? U : T;
type LastInTuple<T extends any[]> = T[LengthOfTuple<DropFirstInTuple<T>>];
type Stuff = [number, boolean, '10'];
type last = LastInTuple<Stuff> //'10'
Yes, it is possible in typescript.
type Last<T> = T extends [...infer _, infer L] ? L : never
type First<T> = T extends [infer Head, ...infer _] ? Head : never
type Tail<T> = T extends [infer _, ...infer Tail] ? Tail : never
type ReplaceFirst<T> = [[never], ...Tail<T>]
type Validator<T extends Array<any>, Result extends Array<any> = []> =
(T extends []
? Result
: (T extends [infer Head]
? (Head extends Last<Result> ? [...Result, Head]
: [...Result, never]
)
: (T extends [infer Head, ...infer Rest]
? (First<Rest> extends Head
? Validator<Rest, [...Result, Head]>
: Validator<ReplaceFirst<Rest>, [...Result, Head]>)
: never)
)
)
const builder = <
Prop extends PropertyKey,
Value extends string,
Elem extends Record<Prop, Value>,
Tuple extends Elem[]
>(tuple: [...Tuple] & Validator<[...Tuple]>) => tuple
const result = builder([
{}, //ok
{ name: 'name' }, //ok
{ name: "name", age: 'age' }, //ok
{ age: 'age' } // error
])
Elem
- infered element from the array/tuple
Tuple
- infered array/tuple
Validator
- iterates throught the infered tuple/array and checks whether next element (Rest[0] extends Head
) extends previous one. If yes, call recursive Validator
with this element, otherwise call Validator
with never
instead of invalid element. 'T extends [infer Head]' - before the last call, checks whether element extends last element from Result
. If yes - push Element
to Result
and return Result
, otherwise push never
to Result
.
I have used [...Tuple] & Validator<[...Tuple]>
to merge validated tuple with provided as an argument. In this way TS is able to highlight only invelid argument instead of whole argument.
AFAIK, it it impossible to do without extra function, because you need to infer each element.
If you want to extract last element from the array, you can do this:
type Last<T> = T extends [...infer _, infer L] ? L : never
type First<T> = T extends [infer Head, ...infer Tail] ? Head : never
Please see documentation for variadic tuple types.
If you are interested in tuple manipulation, you can check my article
P.S. This solution was ok before variadic tuple types.