Refining types in a series of statements

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.

Answer №1

Your code for the ExtractAssertions section is perfectly fine (although you might be able to simplify it without using a recursive conditional type). The issue seems to arise when the multiAssert() function is called, as the type inference for assertions and A ends up being too broad and treated as an unordered array type instead of a tuple type required by ExtractAssertions.

To refine this inference, you can specify A as a const type parameter, providing TypeScript with a hint for narrower inference:

function multiAssert<T, const A extends Asserter<T, any>[]>(
    item: T,
    assertions: A
): asserts item is T & ExtractAssertions<T, A> {
    assertions.forEach(assertion => assertion(item));
}

With these changes, the function now behaves as intended:

const data: Base = { id: "aas-aa", name: "frank", age: 30 };    
multiAssert(data, [assertName, assertAge]);
// ^? function multiAssert<Base, [Asserter<Base, WithName>, Asserter<Base, WithAge>]>(⋯)
data
//^? const data: Base & WithName & WithAge

Link to Playground with Code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unit testing Jest for TypeScript files within a module or namespace

Recently, I've joined a mvc.net project that utilizes typescript on the frontend. There are numerous typescript files wrapped within module Foo {...}, with Foo representing the primary module or namespace. All these typescript files are transpiled in ...

Utilize an array as the response model in Amazon API Gateway using the AWS CDK

I am currently in the process of developing a TypeScript AWS CDK to set up an API Gateway along with its own Swagger documentation. One of the requirements is to create a simple endpoint that returns a list of "Supplier", but I am facing challenges in spec ...

Show the textbox automatically when the checkbox is selected, otherwise keep the textbox hidden

Is it possible to display a textbox in javascript when a checkbox is already checked onLoad? And then hide the textbox if the checkbox is not checked onLoad? ...

Is there a way to incorporate margins into a React component using TypeScript?

I am currently facing a challenge in passing CSS attributes to a component that I am working with. The reason behind this is that I need to modify the margins to fit a specific space, hence my attempt to directly pass the margins. Does anyone have any sug ...

Angular 2 is throwing an error, stating that Observable is not defined

I'm currently working with Observable and ChangeDetectionStrategy to notify other components about any changes that occur. However, I am encountering an issue where the Observable object addItemStream is coming up as undefined. Can anyone spot what mi ...

Instructions on enabling Angular 2 to detect dynamically added routerLink directive

As illustrated in this Plunker, I am dynamically injecting HTML into one of my elements, which can be simplified as follows: @Component({ selector: 'my-comp', template: `<span [innerHTML]="link"></span>`, }) export class MyCo ...

Invoking a functionality within a stream of events through an observable's subscribe

Service2.ts public flags$: BehaviorSubject<FlagName> = new BehaviorSubject<FlagName>("custom-flag-1"); This flag is set up as follows: private _setFlags = () => { const flagsData = this._customClient.getFlags(); if (f ...

Can you identify the type of component being passed in a Higher Order Component?

I am currently in the process of converting a protectedRoute HOC from Javascript to TypeScript while using AWS-Amplify. This higher-order component will serve as a way to secure routes that require authentication. If the user is not logged in, they will b ...

Organize elements within an array using TypeScript

I have an array that may contain multiple elements: "coachID" : [ "choice1", "choice2" ] If the user selects choice2, I want to rearrange the array like this: "coachID" : [ "choice2", "choice1" ] Similarly, if there are more tha ...

Using custom Components to accept HTML input

I have recently developed a custom component to arrange content within IonCardContent. It has been effective for my current requirements: interface ContainerProps { position?: string; content?: string, colour?: string; custClass?: string; } ...

What is the best method for connecting a ref to a component that I am duplicating with React.cloneElement?

Hi everyone! I'm trying to pass a ref into my component so that I can access the variables on the component like state. The only problem is, I'm having trouble getting it to work. It needs to be functional for both classes and functions. Every t ...

Exploring the NextPage type in Next.js

Could someone provide an explanation of the NextPage type within a Next.js TypeScript project? Most sources mention that it is used for type assignment in Next.js, but I am curious about its practical purpose. When and why should we utilize this type? Wha ...

Leveraging the power of TypeScript and Firebase with async/await for executing multiple

Currently, I am reading through user records in a file line by line. Each line represents a user record that I create if it doesn't already exist. It's possible for the same user record to be spread across multiple lines, so when I detect that it ...

Error occurs when attempting to instantiate a class with parameters that do not match any available constructor signatures

I am attempting to encapsulate each instance of router.navigateByUrl within a class function, with the intention of calling that function in the appropriate context. However, I am encountering an error that states 'Supplied parameters do not match any ...

What could be causing the rapid breakage of the socket in Ionic 3's Bluetooth Serial after just a short period

Although the code appears to be functioning correctly, it loses connection shortly after establishing it. This snippet contains the relevant code: import { Component } from '@angular/core'; import { Platform, NavController, ToastController, Ref ...

Limiting the number of items shown in the dropdown panel of an NgSelect

Currently, I am utilizing Angular ngselect to showcase a dropdown menu with multiple options. However, due to the limited screen space, I need to restrict the number of items visible in the dropdown to about 3, allowing users to scroll through the rest. W ...

Error: The module parsing process failed due to the presence of an octal literal in strict mode. To resolve this issue,

I'm currently attempting to incorporate the following regular expression into Angular6: const regexp = new RegExp('^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)(\/|-|\.)(?:0?[1,3-9]|1[0-2])\\2))(? ...

Can someone show me how to implement RequestPromise in TypeScript?

I recently integrated the request-promise library into my TypeScript project, but I am facing some challenges in utilizing it effectively. When attempting to use it in the following manner: import {RequestPromise} from 'request-promise'; Reque ...

Using Typescript to import an npm package that lacks a definition file

I am facing an issue with an npm package (@salesforce/canvas-js-sdk) as it doesn't come with a Typescript definition file. Since I am using React, I have been using the "import from" syntax to bring in dependencies. Visual Studio is not happy about th ...

What is the process for consumers to provide constructor parameters in Angular 2?

Is it possible to modify the field of a component instance? Let's consider an example in test.component.ts: @Component({ selector: 'test', }) export class TestComponent { @Input() temp; temp2; constructor(arg) { ...