Tips for implementing type safety in a generic class to verify that the response type aligns with the anticipated type in TypeScript

In TypeScript, I have created a QueryFactory class with two generic type parameters: TQuery and TResponse. My goal is to ensure type safety so that if the TResponse type does not match the expected response type of the TQuery type, the compiler will throw an error. Below is a snippet of my code:

// Object examples

class Person {
  name: string;
  age: number;
  gender: string;
}

class Book {
  name: string;
  author: string;
}

// Classes 

interface IBaseRequest {}

class RequestBase<T> implements IRequest<T>, IBaseRequest { }

interface IRequest<TResponse> extends IBaseRequest { }  

class GetPersonByIdQuery extends RequestBase<Person> implements IRequest<Person>, IBaseRequest { }

class QueryFactory<TQuery, TResponse> { }

// Implementation

new QueryFactory<GetPersonByIdQuery, Person>(); // This is allowed as expected.

new QueryFactory<GetPersonByIdQuery, Book>(); // This should trigger a compilation error
new QueryFactory<GetPersonByIdQuery, string>(); // This should also result in a compilation error

I am looking for a way to enforce that the TResponse type provided to QueryFactory matches the expected response type of the TQuery type. Specifically, the GetPersonByIdQuery class should only permit Person as the response type. Is there a method to implement this type safety check in TypeScript? How can I modify the QueryFactory class to achieve this?

Answer №1

To restrict the constraints on the generic type parameters of QueryFactory, you can do this:

class QueryFactory<
    Q extends IRequest<any>,
    R extends ResponseForQuery<Q>
> { }

The helper type ResponseForQuery<Q> is defined as:

type ResponseForQuery<Q> =
    Q extends IRequest<infer T> ? T : never;

Here, the first type parameter Q must be assignable to some IRequest<T> for a specific type T, while R is constrained to be ResponseForQuery<Q>, which infers the actual type based on Q's compatibility with IRequest<T>.

This computation using conditional type inference requires a structural dependence between the generic type parameter and its usage within IRequest<T>. If there's no such dependence, like in your example IRequest<T>, where T isn't referenced structurally, then the inference defaults to the unknown type.


In cases where IRequest<T> does have a structural dependence on T, allowing proper distinction between different types, the inference works correctly:

interface IRequest<T> extends IBaseRequest {
    invariance: (x: T) => T; // Hypothetical dependency
}

type X = ResponseForQuery<GetPersonByIdQuery>;
//   ^? type X = Person

new QueryFactory<GetPersonByIdQuery, Person>(); // No error
new QueryFactory<GetPersonByIdQuery, Book>(); // Error
new QueryFactory<GetPersonByIdQuery, string>(); // Error

By ensuring a valid structural dependence, you allow TypeScript to make accurate inferences and apply constraints effectively.

Your unique use case may involve a different form of dependence than the random one demonstrated here, but it's crucial to establish that linkage for successful type inference scenarios.

Playground link to 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

Error in typescript: The property 'exact' is not found in the type 'IntrinsicAttributes & RouteProps'

While trying to set up private routing in Typescript, I encountered the following error. Can anyone provide assistance? Type '{ exact: true; render: (routerProps: RouterProps) => Element; }' is not compatible with type 'IntrinsicAttribu ...

Convert all existing objects to strings

I have a type that consists of properties with different data types type ExampleType = { one: string two: boolean three: 'A' | 'Union' } Is there an easier way to define the same type but with all properties as strings? type Exam ...

Convert TypeScript-specific statements into standard JavaScript code

For my nextjs frontend, I want to integrate authentication using a keycloak server. I came across this helpful example on how to implement it. The only issue is that the example is in typescript and I need to adapt it for my javascript application. Being u ...

Ways to disperse items within another item

I have an inner object nested inside another object, and I am looking to extract the values from the inner object for easier access using its id. My Object Resolver [ { _id: { _id: '123456789', totaloutcome: 'DONE' }, count: 4 }, { ...

Tips for adjusting HighCharts layout with highcharts-vue integrations

I have a fairly simple component: <template> <div> <chart v-if="!loading" ref="priceGraph" constructor-type="stockChart" :options="chartData" ...

Is it feasible to evaluate a Typescript method parameter decorator at request time in a nodejs+nestjs environment rather than just at build time?

Looking to simplify my handling of mongodb calls with and without transactions in a single service method by writing a decorator. This would help eliminate the repetition of code and make things more efficient. Key points for usage: • Service class has ...

Refreshing a page with a 404 error in Angular 2 while in production mode and without the useHash configuration

I've encountered an issue while using Angular 2 without the useHash feature. When trying to visit the URL directly in a browser, I'm getting a 404 not found error. I have searched extensively and attempted various solutions including: Adding L ...

In TypeScript, use a Record<string, any> to convert to {name: string}

I have developed a custom react hook to handle API calls: const useFetch: (string) => Record<string, any> | null = (path: string) => { const [data, setData] = useState<Record<string, any> | null>(null); var requestOptions: Requ ...

Incorporating HTML and JavaScript into TypeScript: How to Embed a Shopify Buy Button in a .tsx document

I am currently looking to integrate Shopify with my personal website. My frontend is built using React (NextJS with TypeScript). The embed code for the Shopify buy button consists of an HTML div tag wrapping JavaScript. I am wondering how I can effectivel ...

Deactivate the Mention and Hash tag in ngx-linkifyjs

I am currently utilizing ngx-linkifyjs to automatically convert URLs in text to clickable hyperlinks. However, I am facing an issue where it is also converting # and @ tags into links. Is there a way to prevent the conversion of # and @ while maintain ...

The modification in Typescript's type order from version 1.7 to 1.8 resulted in a significant

A Visual Studio Cordova application with a unique TypeScript source structure: /src /app /appsub1 appsub1.ts -> 4 : 7 /appsub2 appsub2.ts -> 5 : 6 app.ts -> 3 : 5 /mod1 /mod1sub1 mod1sub1.ts -> 7 : 4 m ...

How can I change a ReactNode into a text format?

I am looking for a way to convert the following code snippet into a string while preserving Tailwind CSS and other elements. My project uses Next.js with TypeScript and Tailwind CSS. Input : export default function Header_1() { return ( <div clas ...

How can you add or remove an item from an array of objects in Angular/RXJS using Observables?

Purpose: The goal is to append a new object to an existing Observable array of objects and ensure that this change is visible on the DOM as the final step. NewObject.ts: export class NewObject { name: string; title: string; } Here's the example ...

ngModelChange doesn't trigger if the value is manually altered

Here is the scenario I am experiencing: //html <input (ngModelChange)="onSelection()" [(ngModel)]="selectedNode" > // in the ts file onSelection() { alert('changed'); } Typing something inside the input tri ...

Developing maintenance logic in Angular to control subsequent API requests

In our Angular 9 application, we have various components, some of which have parent-child relationships while others are independent. We begin by making an initial API call that returns a true or false flag value. Depending on this value, we decide whether ...

How can I access a nested FormArray in Angular?

I have a situation where I am trying to access the second FormArray inside another FormArray. Here is an excerpt from my component: registrationForm = new FormGroup({ registrations: new FormArray([this.patchRegistrationValues()]) }); patchRegistrati ...

What is the best way to accept user input in typescript?

Currently, I am working on a TypeScript project that involves taking user input for the addition of two numbers. Below is the code snippet I am using: function rotatedString(S1,S2){ return S1+S2; } function processData() { //INPUT[uncomment & m ...

I am in need of assistance with incorporating a particular hibernate Inheritance mapping into my project

I am dealing with a situation where I have two classes, parent and child, with a self-referential relationship on the child side. The database is set up with separate tables for both parent and child, sharing the same "id", and using the column "holder" as ...

Having trouble generating a basic TypeScript definition file

I'm having trouble creating a definition file for vue-moment. While it compiles perfectly in IntelliJ, I encounter an issue with vue-cli build: This dependency was not found: * vue-moment in ./src/main.ts In my package.json, I added: "types": "typ ...

How can we define and put into action a function in TypeScript that incorporates certain properties?

I have a vision of creating a message feature that can be invoked directly with overloading capabilities. I also wish to incorporate other function calls within this message feature, such as message.confirm(). Achieving this in TypeScript seems challenging ...