I'm attempting to merge a series of assertions in a safe manner without the need to individually call each one.
For instance, consider the following code:
type Base = { id: string, name?: string, age?: number };
type WithName = { name: string };
type WithAge = { age: number };
export type Asserter<T, U> = (x: T) => asserts x is T & U;
const assertName: Asserter<Base, WithName> = (x: Base): asserts x is Base & WithName => {
if (x.name === undefined) {
throw new Error("missing name");
}
};
const assertAge: Asserter<Base, WithAge> = (x: Base): asserts x is Base & WithAge => {
if (x.age === undefined) {
throw new Error("missing age");
}
};
type ExtractAssertions<T, U extends Asserter<T, any>[]> =
U extends [Asserter<T, infer V>, ...infer Rest]
? Rest extends Asserter<T, any>[]
? V & ExtractAssertions<T, Rest>
: V
: {};
function multiAssert<T, A extends Asserter<T, any>[]>(
item: T,
assertions: A
): asserts item is T & ExtractAssertions<T, A> {
assertions.forEach(assertion => assertion(item));
}
const data: Base = { id: "aas-aa", name: "frank", age: 30 };
multiAssert(data, [assertName, assertAge]);
console.log(data.name[0]); // This should compile correctly
console.log(data.age + 3); // This should also be possible
I seem to be encountering an issue with the ExtractAssertions
type, and despite various attempts, I can't seem to resolve it.
Explore this TS playground link for further insight
I've experimented with multiple versions of the recursive ExtractAssertions
type, but they all eventually circle back to the Base
type or end up at never
.