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

Trouble accessing images from database in Angular 2 with Firebase

Recently, I've integrated an image upload feature for my database using the following function: private saveFileData(upload: Upload): void { this.firebaseAuth.authState.subscribe(auth => { this.db.list(`uploads/${auth && auth.email && au ...

Achieving the incorporation of multiple components within a parent component using Angular 6

Within parent.component.html The HTML code I have implemented is as follows: <button type="button" class="btn btn-secondary (click)="AddComponentAdd()">Address</button> <app-addresse *ngFor="let addres of collOfAdd" [add]="addres">< ...

Disable and grey out the button while waiting for the Observable to broadcast successfully

component.html <button mat-raised-button color="primary" type="submit"> <mat-icon>account_box</mat-icon> <span *ngIf="!loading">&nbsp;&nbsp;&nbsp;Register</span> <span * ...

ESLint is indicating an error when attempting to import the screen from @testing-library/react

After importing the screen function from @testing-library/react, I encountered an ESLint error: ESLint: screen not found in '@testing-library/react'(import/named) // render is imported properly import { render, screen } from '@testing-lib ...

The incorrect initial state is causing issues in the Zustand state management on the Next.js server side

While utilizing zustand as a global state manager, I encountered an issue where the persisted states were not being logged correctly in the server side of nextjs pages. The log would only show the default values (which are null) and not the updated state v ...

Executing multiple queries in a node.js transaction with Sequelize using an array

I am looking to include the updates on the clothingModel inside a transaction, with the condition that if it successfully commits, then update the reservationModel. This is the snippet of code I am attempting to refactor using sequelize.transaction tr ...

Challenges encountered while compiling Node.js code with ts-node (Error: Cannot use import statement outside a module)

Trying to compile TypeScript code with NodeJS using this command: npx ts-node src/server.ts An error is thrown: SyntaxError: Cannot use import statement outside a module Following the error's instructions: Warning: To load an ES module, set " ...

Exploring through objects extensively and expanding all their values within Angular

I am in need of searching for a specific value within an object graph. Once this value is found, I want to set the 'expanded' property to true on that particular object, as well as on all containing objects up the object graph. For example, give ...

Navigating through JSON object using Angular 2's ngFor iterator

I want to test the front end with some dummy JSON before I write a service to get real JSON data. What is the correct way to iterate through JSON using ngFor? In my component.ts file (ngOnInit()), I tried the following code with a simple interface: var js ...

Is it possible for FormArray to return null?

Hello there. I've attempted various methods, but none of them seem to be effective. Currently, I am working on this task where I need to start a formArray for emails. email: [testestest] However, what I have is: email: [testestest] I'm encoun ...

React-Redux: Unable to access the 'closed' property as it is undefined

Encountered a problem when using dispatch() in React-Redux. Specifically, the action below: export const fetchMetrics = () => { dispatch(fetchMetricsBegin); APIService.get('/dashboard/info/') .then((response) => { ...

Sending an ID from an array within a *ngFor loop to a different component in Angular: a step-by-step guide

I have a collection of items that I loop through using ngFor. My goal is to pass all its attributes to another component. I attempted to accomplish this with the following code: *ngFor='let item of postList [routerLink]="['/detailed-post&ap ...

TS - Custom API hook for making multiple API requests - incompatible type with 'IUseApiHook'

What is my objective? I aim to develop a versatile function capable of handling any type of API request for a frontend application. Essentially, I want to add some flair. Issue at hand? I find myself overwhelmed and in need of a fresh perspective to revi ...

typescript: How to restrict an array's type in a specific order

Is there a way to restrict the types of elements in an array in TypeScript without specifying paradigms? For example, instead of defining arrays as follows: const arr:Array<any> = [] I would like to be able to specify a specific order for the arr ...

Adjust the property to be optional or required depending on the condition using a generic type

const controlConfig = >T extends 'input' | 'button'(config: Config<T>): Config<T> => config; interface Config<TYPE extends 'input' | 'button'> { type: TYPE; label: string; ...

Why does tsc produce a compiled file that throws an exception when executed, while ts-node successfully runs the TypeScript file without any issues?

I have written two ts files to test a decorator. Here is the content of index.ts: import { lockMethod } from './dec'; class Person { walk() { console.info(`I am walking`); } @lockMethod run() { console.info(`I am running`); } ...

How can you customize the appearance of the filledInput component within a TextField component in Material UI?

I need some guidance on how to change the color of the FilledInput component within a TextField. Unlike InputProps, FilledInputProps are not directly accessible for styling with classes. Any suggestions on how I can customize the styling of the FilledInpu ...

Angular2 Cache: Enhance Your Application's Performance

Currently seeking a cache solution for my Angular2 application. Imagine we have a massive collection of Movie objects stored on a server, too many to fetch all at once. The server offers a REST endpoint: getMovie(String id) On the client side, I need a s ...

Error message: When working with Protobuf, an uncaught reference error occurs because the 'exports

Currently, I am in the process of configuring protobuf to work with typescript. According to the official Google Documentation, all that is needed is to execute npm install google-protobuf and then to include require('google-protobuf'). Unfortu ...

I encountered an issue where I did not receive a response when utilizing res.write() within the fetch function

Currently, I am utilizing the <res.write()> method in nodejs at https://nodejs.org/api/http.html#responsewritechunk-encoding-callback. In addition to this, I am also implementing the fetch function which can be found at https://developer.mozilla.org/ ...