Creating TypeScript versions of `delegate` pattern JavaScript code

Looking for a way to convert the following javascript code into typescript?

const handlers = {
  say (msg) {
    console.log(msg)
  },
  add (a, b) {
    return a + b
  }
}

function caller (method, ...args) {
  if (handlers[method]) return handlers[methd](...args)
  throw new Error(`${method} not found`)
}

I have a collection of more than 100 functions in handlers spread across multiple files. These handlers serve as APIs for other modules, while the caller serves as a common entry point for these APIs.

All the functions have been successfully rewritten in typescript. However, when it comes to the caller function, I am unsure about how to maintain type information for the callees.

Various attempts have been made, but none have proven successful in the end :( The solution below is one that shows promise.

type AlterReturnType<K, T extends (...args: any[]) => any> =
  T extends () => any ? (k: K) => ReturnType<T> :
  T extends (a: infer A) => any ? (k: K, a: A) => ReturnType<T> :
  T extends (a: infer A, b: infer B) => any ? (k: K, a: A, b: B) => ReturnType<T> :
  T extends (a: infer A, b: infer B, c: infer C) => any ? (k: K, a: A, b: B, c: C) => ReturnType<T> :
  T extends (a: infer A, b: infer B, c: infer C, d: infer D) => any ? (k: K, a: A, b: B, c: C, d: D) => ReturnType<T> :
  T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E) => any ? (k: K, a: A, b: B, c: C, d: D, e: E) => ReturnType<T> :
  never

type Handlers = typeof handlers
type HandleKeys = keyof Handlers

interface IHandlerObject {
  [k: string]: (...args: any[]) => any
}

type GetCaller<T extends IHandlerObject> = {
  [k in keyof T]: AlterReturnType<k, T[k]>
}

type Caller = GetCaller<Handlers>[keyof Handlers]

const caller: Caller = function name (method: string, ...args: any[]) {
  // do know how to rewrite the following
  // @ts-ignore
  if (handlers[method]) return handlers[method](...args)
  throw new Error(`${method} not found`)
}

// TS error occurred:
//   [ts] Cannot invoke an expression whose type lacks a call signature. Type '((k: "say", a: any) => void) | ((k: "add", a: any, b: any) => any)' has no compatible call signatures.
caller('say', 'ggg')

Answer №1

Successfully combining the appropriate call signatures into a union is a step in the right direction, but what you really require is an intersection. Delving into src/compiler/checker.ts, I uncovered a method involving contravariant type inference to achieve this:

type User1 = GetUser<Managers>;
type User =
  {[k in keyof User1]: (p: User1[k]) => void} extends
    {[n: string]: (p: infer S) => void} ? S : never;

Note: Visit Convert union type to intersection type for a related technique with detailed explanations.

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

The Angular 2 router UMD file, router.umd.js, was not found

Trying to run an Angular 2 project and implement @angular/router is proving to be a bit challenging. Everything seems to be working fine, until the moment I attempt: import { provideRouter, RouterConfig } from '@angular/router'; As it tries to ...

Implementing handleRequest as an asynchronous function within the passportjs guard

@Injectable() export class RefreshAuthGuard extends JwtAuthGuard { constructor( private readonly jwtService: JwtService, ) { super(); } public handleRequest(err: any, user: any, info: Error, ctx: any): any { if (err ...

What is the best way to create a function that can identify and change URLs within a JSON file?

I'm currently working on a function that will replace all URLs in a JSON with buttons that redirect to another function. The modified JSON, complete with the new buttons, will then be displayed on my website. In my component.ts file, the section wher ...

Encountered issue with accessing the Error Object in the Error Handling Middleware

Below is the custom error validation code that I have developed : .custom(async (username) => { const user = await UserModel.findOne({ username }) if (user) throw new ConflictError('username already used& ...

Steps for setting up a project to compile for ES6 syntax:

Working on a project using Angular 2 + PrimeNG, I encountered an issue with TypeScript compilation while trying to use Git. The solution involved adjusting the package.json file. "dependencies": { "@angular/common": "2.4.2", // List of dependencies goes ...

Facing the issue once more - Angular displaying "Http failure response for... 0 Unknown Error"

I have been researching extensively on this issue, particularly on Stack Overflow. Many of the responses point to it being a CORS problem, but I am uncertain if that is the case in my situation. Therefore, I am reaching out for help once again and would gr ...

Changing a date format in typescript: Here is how you can easily convert a date from one

Using React with Typescript: I am currently working with a date picker from material-ui version 5. The date picker requires the date value to be in the format "yyyy-MM-dd". However, the API returns a Date object in the format "2022-01-12T00:00:00.000+00:0 ...

Enhance the TypeScript interface by dynamically appending new fields with specific naming conventions

My interface is structured like this: interface ProjectCostData { purchasePrice: number; propertyValue: number; recentlyDamaged: boolean; } Now I am looking to dynamically create a new interface based on the one above: interface ProjectCostDataWithS ...

Retrieve information using Angular's EventEmitter

Recently I started learning Angular and encountered a challenging issue that has kept me occupied for the past few hours. I have implemented a parent-child relationship between two components, with a need to share a boolean variable from the parent to the ...

Learn how to display or conceal the HTML for 'Share this' buttons on specific routes defined in the index.html file

Currently, I am in the process of updating an existing Angular application. One of the requirements is to hide the "Share this buttons" on specific routes within the application. The "Share" module typically appears on the left side of the browser window a ...

Django and Angular combine to create a floral mapping feature that allows users to easily return to their task list

I am looking to arrange the output from the flower library (/api/tasks) into a list of objects. The current response includes multiple objects, but lacks a "list wrapper", making it difficult to iterate over. API: An example of the return is as follows: H ...

Creating dynamic routing functionality in Angular 8 allows for a more personalized and

I am struggling with setting up routing in Angular 8. Here is how I am trying to do it: 'company/:id/activity' 'company/:id/contacts' However, I am not receiving any params in the activatedRoute: this.activateRoute.params ...

Using TypeScript to deserialize JSON into a Discriminated Union

Consider the following Typescript code snippet: class Excel { Password: string; Sheet: number; } class Csv { Separator: string; Encoding: string; } type FileType = Excel | Csv let input = '{"Separator": ",", "Encoding": "UTF-8"}&ap ...

How can I achieve this using JavaScript?

I am attempting to create a TypeScript script that will produce the following JavaScript output. This script is intended for a NodeJS server that operates with controllers imported during initialization. (Desired JavaScript output) How can I achieve this? ...

Could you lend a hand in figuring out the root cause of why this Express server is constantly serving up error

I am encountering a 404 error while running this test. I can't seem to identify the issue on my own and could really use another set of eyes to help me out. The test involves mocking a request to the Microsoft Graph API in order to remove a member fro ...

Utilize the <wbr> tag within FormattedMessage and assign it as a value while coding with TypeScript

Trying out the optional word break tag <wbr> in a message within <FormattedMessage id="some:message" />. Context Some words or texts are too lengthy for certain parent elements on smaller mobile screens, and we have a column layout t ...

Troubleshooting: Angular 13 input radio not recognizing checked condition

Storing selectedKitchenId in localstorage, and checking if selectedKitchenId === kitchen.id to determine which radio button should be selected. Cannot figure out why the checked condition is not working as expected, even though when I use a strong tag to d ...

Do you believe this problem with transpilation has been properly reported to babel-jest?

I recently encountered a problem in the jest project regarding babel-jest transpilation. I added some code that appeared to be error-free, but caused transpilation to fail completely. Although the issue seemed related to a Typescript Next project, there a ...

Using Angular Form Builder to assign a value depending on the selected option in a dropdown menu

My approach to Angular Form Builder initialization includes a group that looks like this: contactReason: this.formBuilder.group({ description: '', source: this.sourceType() }) For the 'description' field, I hav ...

Is it possible to synchronize the Lit cached DOM with the live DOM?

Utilizing the Lit framework for constructing my front-end UI components has been a game-changer. However, I have encountered an issue while incorporating our internal company design system's web components. One of these components has the ability to r ...