Determine the return type based on the input parameter and a mapping strategy

I am encountering an issue with the inferred returnType of the following types. The problem lies in the cityAttractions type, where it should ideally have a type error in its return statement for returning a string instead of an array of strings.

Playground for TypeScript

interface Tool<RESULT = any> {
  returnType?: RESULT;
}

type ToolCallWithInferredReturnType<ToolsRecord extends Record<string, Tool>> = {
  [ToolName in keyof ToolsRecord]: ToolsRecord[ToolName]['returnType']
}

export type OnToolCall<ToolsRecord extends Record<string, Tool> = Record<string, Tool>> = 
  (args: { toolCall: { toolName: string } }) => ToolsRecord extends Record<string, Tool>
    ? ToolCallWithInferredReturnType<ToolsRecord>[typeof args['toolCall']['toolName']] 
    : void | Promise<unknown> | unknown;

type Tools = {
  weather: {
    returnType: string;
  }
  cityAttractions: {
    returnType: string[];
  }
}

export const onToolCall: OnToolCall<Tools> = ({ toolCall }) => {
  if (toolCall.toolName === 'weather') {
    return 'Weather information was shown to the user.';
  }
  if (toolCall.toolName === 'cityAttractions') {
    return "Attractions was shown to the user."; // type error should be throw here
  }

  throw Error("")
}

Answer №1

To make this setup function, you must define OnToolCall<T> as a generic function. The return type of OnToolCall<T> needs to be flexible based on the type of the toolName property in the argument. You can't achieve this by simply referring to the type as

typeof args['toolCall']['toolName']
. If args is of type
{ toolCall: { toolName: string} }
, then that simply means string. Generics are essential in this scenario.

Imagine that the type of the toolName property is a generic type parameter K restricted to keyof T. In that case, the definition of OnToolCall<T> should be as follows:

type OnToolCall<T extends Record<string, Tool>> =
  <K extends keyof T>(args: { toolCall: { toolName: K } }) =>
    ToolCallWithInferredReturnType<T>[K];

By doing this, the expected behavior can be validated by the callers:

type Tools = {
  weather: {
    returnType: string;
  }
  cityAttractions: {
    returnType: string[];
  }
}

let onToolCall: OnToolCall<Tools> = ⋯;

const strArr = onToolCall({ toolCall: { toolName: "cityAttractions" } });
//    ^? const strArr: string[]
const str = onToolCall({ toolCall: { toolName: "weather" } });
//    ^? const str: string

Everything seems to be in order here.


It's crucial to note that the previous way of implementing onToolCall won't work without a type assertion:

onToolCall = ({ toolCall }) => { // error!
//~~~~~~~~ <-- Type 'string[] | string' is not assignable to 
//  type 'ToolCallWithInferredReturnType<Tools>[K]'
  if (toolCall.toolName === 'weather') {
    return 'Weather information was shown to the user.';
  }
  if (toolCall.toolName === 'cityAttractions') {
    return ["Attractions was shown to the user."]; 
  }
  throw Error("")
}

Control flow narrowing, such as using if/else blocks, does not influence generic type parameters in TypeScript. The challenge lies in verifying that string is assignable to

ToolCallWithInferredReturnType<Tools>[K]
.

A request to address this issue has been open for a while at microsoft/TypeScript#33014. Although progress has been made in implementing it, the feature is not currently available in the language (as of TypeScript 5.5).

Maybe in a future TypeScript version, the code mentioned will function as intended. For now, when developing a function that returns a generic indexed access type, resort to object indexing rather than relying on if/else statements.

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

What could be the root cause behind the error encountered while trying to authenticate a JWT?

I've been working on integrating a Google OAuth login feature. Once the user successfully logs in with their Google account, a JWT token is sent to this endpoint on my Express server, where it is then decoded using jsonwebtoken: app.post('/login/ ...

Issues with manipulating state using TypeScript Discriminated Unions"Incompatibility with setState

Imagine having a unique type structure like the one shown below: export type IEntity = ({ entity: IReport setEntity: (report: IReport) => void } | { entity: ITest setEntity: (test: ITest) => void }); Both the entity and setEntity fun ...

Trouble arises when attempting to modify a property inherited from a parent class in TypeScript while

I've defined a base class like the following: import Vue from "vue"; class ComponentBase extends Vue { constructor() { super(); this.left = 100; this.top = 100; this.isSelected = false; } public left: numb ...

Unable to utilize the forEach() function on an array-like object

While I generally know how to use forEach, I recently encountered a situation that left me puzzled. Even after searching online, I couldn't find any new information that could help. I recently started delving into TypeScript due to my work with Angul ...

Export an array of objects using the Angular XLSX library

Here is my example data: exampleData: any[] = [ { "id": "123", "requestType": "Demo", "requestDate": "12/05/21", "status": "Success", "product": [ { "productName": "example product A", "productQty": "8" ...

Create a typescript class object

My journey with Typescript is just beginning as I delve into using it alongside Ionic. Coming from a background in Java, I'm finding the syntax and approach quite different and challenging. One area that's giving me trouble is creating new object ...

Tips for successfully passing the dynamic state and setState to a function in typescript

I'm encountering an issue with passing dynamic state and setState into a function using Typescript. Despite trying to use Generics, I am facing complaints from Typescript. Here is the code snippet: const handleSelectTag = (tag: { color: string; labe ...

Unselecting a line will result in the disabling of all chart lines

Currently, I am working with Primeng and incorporating multiple charts into my view. One feature of Primeng that I have successfully implemented is disabling lines. I am following this particular example: Primeng provides a handy function on their site: ...

Efficient access to variable-enumerated objects in TypeScript

Let's say I have the following basic interfaces in my project: interface X {}; interface Y {}; interface Data { x: X[]; y: Y[]; } And also this function: function fetchData<S extends keyof Data>(type: S): Data[S] { return data[type]; } ...

What is the best way to incorporate TypeScript variables into CSS files?

In my Angular project, I am aiming to utilize a string defined in Typescript within a CSS file. Specifically, I want to set the background image of a navbar component using a path retrieved from a database service. Although I came across suggestions to use ...

Steps for reinstalling TypeScript

I had installed TypeScript through npm a while back Initially, it was working fine but turned out to be outdated Trying to upgrade it with npm ended up breaking everything. Is there any way to do a fresh installation? Here are the steps I took: Philip Sm ...

What could be causing the rendering issue on this React component page when an id is provided in the React Router URL?

These are the dependencies I am using for my React project: "react": "^16.13.1", "react-dom": "^16.13.1", "react-helmet": "^6.1.0", "react-html-parser": "^2.0.2", "react-i ...

Encountering a 504 Gateway Timeout error while attempting to send a POST request to an API route in a NEXT.JS application that

I encountered a 504 error gateway timeout when attempting to post a request to api/webhooks, and in the Vercel log, I received a Task timed out after 10.02 seconds message. This code represents a webhook connection between my clerk and MongoDB. [POST] [m ...

Is it possible to apply a formatting filter or pipe dynamically within an *ngFor loop in Angular (versions 2 and 4

Here is the data Object within my component sampleData=[ { "value": "sample value with no formatter", "formatter": null, }, { "value": "1234.5678", "formatter": "number:'3.5-5'", }, { "value": "1.3495", "formatt ...

Problem with Change Detection in Angular 2

I am currently utilizing Angular 2.1.1 for my application. Situation Let's consider a scenario where two components are interacting with each other. The component DeviceSettingsComponent is active and visible to the user. It contains a close button ...

How to Modify CSS in Angular 6 for Another Element in ngFor Loop Using Renderer2

I have utilized ngFor to add columns to a table. When a user clicks on a <td>, it triggers a Dialog box to open and return certain values. Using Renderer2, I change the background-color of the selected <td>. Now, based on these returned values, ...

Ensuring TypeScript's strict null check on a field within an object that is part of an

When using TypeScript and checking for null on a nullable field inside an object array (where strictNullCheck is set to true), the compiler may still raise an error saying that 'Object is possibly undefined'. Here's an example: interface IA ...

Displaying a segment of information extracted from a JSON array

I'm currently tackling a project that involves using React, Redux, and TypeScript. Within the JSON file, there is a key-value pair: "data_start": "2022-09-02" Is there a way to display this date in a different format, specifical ...

Can you explain the variance between the (Record<string, unknown>) and object type?

Can you explain the distinction between type Record<string, unkown> and type object? Create a generic DeepReadonly<T> which ensures that every parameter of an object - and its nested objects recursively - is readonly. Here's the type I c ...

I'm curious if anyone has experimented with implementing TypeScript enums within AngularJS HTML pages

During my Typescript project, I defined an enum like this: enum Action { None = 0, Registering = 1, Authenticating = 2 }; In the controller, I declared a property named action as follows: class AuthService implements IAuthService { action: number; ...