In TypeScript, encountering an unanticipated intersection

In my "models" registry, whenever I select a model and invoke a method on it, TypeScript anticipates an intersection of parameters across all registered models.

To demonstrate this issue concisely, I've created a dummy method called "getName".

export class Model<N extends string> {
  public name: N;

  constructor(name: N) {
    this.name = name;
  }

  public getName = (options: { __type: N }) => options.__type;
}

export const Book = new Model("Book");
export const User = new Model("User");

export const modelRegistry = { Book, User };

export type ModelRegistry = typeof modelRegistry;

export const makeModel = <N extends keyof ModelRegistry>(name: N) => (
  options: Parameters<ModelRegistry[N]["getName"]>[0],
) => {
  const model = modelRegistry[name];
  return model.getName(options); // <-- bug: TS expects this to be { __type: User } & { __type: Book }
};

https://i.stack.imgur.com/ZGwcg.png

Playground Link

Answer №1

One issue arises when the compiler struggles to decipher the generic ModelRegistry[N]['getName'] in relation to the type N during a function call. As a result, it broadens N to encompass the entire union type keyof ModelRegistry, causing model.getName to be seen as a union of different parameter types for functions. Previously, before TypeScript 3.3, such unions were not callable at all (refer to microsoft/TypeScript#7294). In TypeScript 3.3, support was introduced to allow calling these functions with an intersection of parameters from each function within the union (see this link). Although an improvement from being "not callable," this approach still has limitations, especially when dealing with correlated union types (refer to microsoft/TypeScript#30581).

In such scenarios, one simple solution is to acknowledge that you understand the situation better than the compiler and use type assertions to specify the expected types. For demonstration purposes, I'll use as any to override the compiler:

export const makeModel = <N extends keyof ModelRegistry>(name: N) => (
  options: Parameters<ModelRegistry[N]['getName']>[0],
): ReturnType<ModelRegistry[N]['getName']> => {
  const model = modelRegistry[name];
  return model.getName(options as any) as any; // Outsmarting the compiler 🤓
};

This approach should resolve the issue for you. You can refine the use of any assertions to more suitable types if needed. Note that I manually specified the return type of the nested makeModel call as

ReturnType<ModelRegistry[N]['getName']
, as the compiler may struggle to infer this on its own.

In conclusion, I hope this information proves helpful. Best of luck!

Answer №2

To overcome this issue, one approach is to create a general version of the makeModel function that can operate on any registry, and then establish a specific version for a particular instance of that registry:

export const makeAnyModel = <Registry extends { [N in string]: Model<N> }>
(registry: Registry, name: keyof Registry) => (
  options: Parameters<Registry[keyof Registry]["getName"]>[0],
) => {
    const model = registry[name];
    return model.getName(options);
};

export const makeModel = <N extends keyof ModelRegistry>(name: N) => 
    makeAnyModel(modelRegistry, name);

The makeAnyModel function does not encounter errors because the type N is automatically assumed to be of type string during implementation checking - thus, the inferred type for getName is as follows:

Model<string>.getName: (options: { __type: string; }) => string

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

Angular is throwing an error stating that the type '{ }[]' cannot be assigned to the type '[{ }]'

I'm in need of assistance and clarification regarding the error I encountered in my application... When I receive a JSON response from an API with some data that includes an array of products, I aim to extract these products (izdelki) from the array, ...

Tips for telling the difference between typescript Index signatures and JavaScript computed property names

ngOnChanges(changes: {[paramName: string]: SimpleChange}): void { console.log('Any modifications involved', changes); } I'm scratching my head over the purpose of 'changes: {[propName: string]: SimpleChange}'. Can someone cl ...

Tips on using class-validator @isArray to handle cases where only a single item is received from a query parameter

Currently, I am attempting to validate a request using class-validator to check if it is an array. The inputs are sourced from query parameters like this: /api/items?someTypes=this This is what my request dto resembles: (...) @IsArray() @IsEn ...

Converting the following ngRx selector to a boolean return – a guide

I am currently using the following filter to retrieve a list of data within a specific date range. I have implemented logic in the component where I check the length of the list and assign True or False to a variable based on that. I am wondering if ther ...

Running multiple instances of Chrome to execute all scenarios sequentially within a single feature file in Protractor

I need to run all scenarios in multiple instances of a browser. I've set the maximum instance value in capabilities, but only one instance of Chrome opens and the tests run sequentially. How can I ensure that the tests run in multiple instances simult ...

Using Svelte with Vite: Unable to import the Writable<T> interface for writable store in TypeScript

Within a Svelte project that was scaffolded using Vite, I am attempting to create a Svelte store in Typescript. However, I am encountering difficulties when trying to import the Writable<T> interface as shown in the code snippet below: import { Writa ...

The use of custom loaders alongside ts-node allows for more flexibility

Is it possible to utilize ts-node with a custom loader? The documentation only mentions enabling esm compatibility. ts-node --esm my-file.ts I am attempting to implement a custom loader for testing an ESM module, but I prefer not to rely on node for compi ...

How do I set up middleware with async/await in NestJS?

I am currently integrating bull-arena into my NestJS application. export class AppModule { configure(consumer: MiddlewareConsumer) { const queues = this.createArenaQueues(); const arena = Arena({ queues }, { disableListen: true }); consumer. ...

Retrieve all elements within an Angular6 Directive that share the same name as the Directive

I have created a custom Directive called isSelected and applied it to several elements in different components as shown below: <i isSelected class="icon"></i> <i isSelected class="icon"></i> <i isSelected class="icon"></i ...

Ways to integrate npm dependencies into your Cordova plugin

Currently working on implementing a Cordova plugin called core-cordova found in this repository. This particular plugin has a dependency on another NPM package. The issue arises after installing the plugin in my app using: $ cordova plugin add @aerogears ...

Is there a way to transform a date from the format 2021-11-26T23:19:00.000+11:00 into 11/26/2021 23:19?

Having trouble formatting the date in MM/DD/YYYY HH:mm:ss format within my .ts script. I attempted to use moment.js. moment(dateToBeFormatted, "'YYYY-MM-dd'T'HH:mm:ss.SSS'Z'").format("YYYY/MM/DD HH:mm") However ...

TypeScript will show an error message if it attempts to return a value and instead throws an

Here is the function in question: public getObject(obj:IObjectsCommonJSON): ObjectsCommon { const id = obj.id; this.objectCollector.forEach( object => { if(object.getID() === id){ return object; } }); throw new Erro ...

Issue with Angular: Unable to properly sort data while modifying queryParams

Within the component.ts file: export class TabsComponent implements OnInit { constructor( private store$: Store<UsersState>, private router: ActivatedRoute ) {} ngOnInit(): void { this.onFilterByIncome(); this.router.queryParam ...

Converting docx files to PDF in Angular 15 using the "docxjs" library: A step-by-step guide

I am currently utilizing the to generate some docx files and enable downloading, but I am faced with the challenge of converting these files into PDF format. This is my current process: public download(data: any): void { const documentCreator = new D ...

Sporadic UnhandledPromiseRejectionWarning surfacing while utilizing sinon

Upon inspection, it appears that the objects failApiClient and explicitFailApiClient should be of the same type. When logging them, they seem to have identical outputs: console.log(failApiClient) // { getObjects: [Function: getObjects] } console.log(expli ...

Utilizing getServerSideProps in the new app router (app/blah/page.tsx) for maximum functionality

I am a beginner in Next.js and I am currently experimenting with the new app router feature by placing my pages under app/.../page.tsx The code snippet provided works when using the page router (pages/blah.tsx) but encounters issues when used in app/blah/ ...

Using a split string to destructure an array with a mix of let and const variables

There is a problem with TS: An error occurs stating that 'parsedHours' and 'parsedMinutes' should be declared as constants by using 'const' instead of 'prefer-const'. This issue arises when attempting to destructure ...

Rows in angular ag grid are vanishing when data is filtered

My ag grid includes custom components for each column, but I'm facing an issue where the components disappear when filtering the data. For example: the component inside the row To apply filtering, I use an input with a filtering function: <data-gr ...

Having trouble using the 'in' operator to search for 'Symbol(StrapiCustomCoreController)' while transitioning Strapi to TypeScript

I'm in the process of converting my strapi project to typescript. I've updated all strapi packages to version 4.15.5 and converted the files to ts extension. However, upon running strapi develop, I encounter the following error: [2024-01-03 10:50 ...

Looking to organize an array of objects containing two string elements (countries) based on the country name using TypeScript or the Lodash library?

Below is an example of an array of objects I am working with: { countries: [{ "country_alpha2_code": "PW", "country_name": "PALAU" },{ "country_alpha2_code": "US&qu ...