Anticipate the desired operations and propose appropriate function identifiers within a generalized class framework

Hey there! 😊

I wanted to revisit this question.

interface Works {
  call(): void;
}

interface DoesntWork {
  value: number;
}

interface ShouldWork {
  value: number;

  call(): void;
}

class Handler<T extends { [K in keyof T]: () => any }> {
  public send<P extends Extract<keyof T, string>>(
    methodName: P,
    payload: T[P],
  ) {
    //
  }
}

const handlerA = new Handler<Works>(); // ok
const handlerB = new Handler<DoesntWork>(); // error: ok
const handlerC = new Handler<ShouldWork>(); // error: should work

handlerA.send('call', () => null); // ok
handlerB.send('value', 23); // shouldn't work
handlerC.send('call', () => null); // ok
handlerC.send('value', 42); // 'value' shouldn't work and shouldn't be suggested

The idea is that Handler should accept ShouldWork due to the presence of at least one function (call) in the interface.

In the case of handlerC.send, it should not suggest value since it is a number and not a function.

You can try this out on the TypeScript playground here.

Answer â„–1

One approach that I would take is to create an AcceptableHandler<T> utility type that transforms T into a version where each property present becomes a zero-argument function.

type AcceptableHandler<T> =
  HasProp<PickByValue<T, () => any>, { needsAtLeastOneZeroArgMethod(): any }>

To explain further, an AcceptableHandler<T> firstly selects properties from T that are zero-argument functions using PickByValue<T, ()=>any>, which works similar to the Pick<T, K> utility type but based on property values instead of keys. Then it undergoes the check for presence of at least one property with

HasProp<⋯, {needsAtLeastOneZeroArgMethod(): any}>
. If there's even one property, nothing happens; otherwise, it gets replaced with
{needsAtLeastOneZeroArgMethod(): any}
, potentially triggering error messages if there are no acceptable arguments for send.

We also need definitions for PickByValue<T, V> and HasProp<T, D>:

type PickByValue<T, V> =
  { [K in keyof T as T[K] extends V ? K : never]: T[K] }

type HasProp<T, D = never> =
  T extends (keyof T extends never ? never : unknown) ? T : D

The first type remaps keys to filter out property keys, while the second type checks for the absence of known keys using a conditional type.

Using this information, we can define Handler<T> as follows:

class Handler<T extends AcceptableHandler<T>> {
  public send<P extends keyof AcceptableHandler<T>>(
    methodName: P,
    payload: T[P],
  ) {
    //
  }
}

Initially, we constrain T to be of type AcceptableHandler<T>, ensuring that T includes at least one property suitable as a zero-argument function:

const handlerA = new Handler<Works>(); // okay
const handlerB = new Handler<DoesntWork>(); // error
// ------------------------> ~~~~~~~~~~
// Type 'DoesntWork' does not satisfy the 
// constraint '{ needsAtLeastOneZeroArgMethod(): any; }'.
const handlerC = new Handler<ShouldWork>(); // okay

Next, we restrict the type parameter P of send to keys found in AcceptableHandler<T>, validating only zero-arg method properties:

handlerA.send('call', () => null); // okay
handlerB.send('value', 23); // error
// ---------> ~~~~~~~
// Argument of type '"value"' is not assignable to 
// parameter of type '"needsAtLeastOneZeroArgMethod"'.
handlerC.send('call', () => null); // okay
handlerC.send('value', 42); // error
// ---------> ~~~~~~~
// Argument of type '"value"' is not assignable to
// parameter of type '"call"'.

It's worth noting that the error on handlerB might seem odd since expecting "value" to be

"needsAtLeastOneZeroArgMethod"</code could lead to runtime issues if wrongly used. However, given that creating <code>handlerB
itself results in an error, subsequent errors are less critical. Resolving the initial error at new Handler<DoesntWork>() should eliminate or provide a more informative error message for the issue at send().

Visit Playground link for 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

What leads to the inability to utilize environment variables in this TypeScript app built with Vue 3?

Currently, I am developing a single page application utilizing Vue 3 and TypeScript. The main purpose of this app is to interact with an API. All the necessary information including the API's URL and key are stored in the 'src\env.js' f ...

Utilize React Dropzone for effortlessly styling elements upon drop action

Currently, I am working on implementing Dropzone in React. My specific requirement is to display a text and a blue border in the center of the Dropzone area when files are dragged onto it (text: "you are inserting files"). This is how my current code look ...

What is the best method for submitting information using AJAX and jQuery?

Here is the HTML code in question: <script src='https://code.jquery.com/jquery-1.12.4.min.js'></script> <p>1</p> <!-- this content gets loaded with <?php echo $item->id; ?> --> <a id='delBtn1' ...

Is Postman cooperating, yet Ajax is not playing nice?

I am facing an issue while trying to make a GET request from a server that stores some account data. The request needs an Authorization header for it to work properly. I was able to retrieve the data successfully using Postman, but when I tried doing the s ...

Using node.js to make an HTTP request and parse JSON data with the

I am currently working on developing a web application using node.js that needs to interact with a PHP API. My goal is to request a JSON object from the PHP API, which I can then use in one of my .ejs templates. Below is the code snippet for my node.js im ...

Delay the execution of the function in AngularJS until the browser has finished rendering completely and the view-model synchronization cycle has ended

I'm uncertain about the appropriate title for this inquiry, so please correct me if I am mistaken. Suppose that upon page refresh (load), I require an animated scroll to an anchor based on the current location's hash (I am aware of ngAnchorScrol ...

Tips on presenting our data using JSON structure

Hey there! I'm new to Next JS and I'm looking to showcase the data from my index.js file in JSON format in my dynamicid.js file. Can anyone guide me on how to achieve this? index.js In my index.js file, I currently display my output but now I ...

Issue with popup display in React Big Calendar

I'm currently working on a calendar project using React-Big-Calendar, but I've run into an issue with the popup feature not functioning properly. <div className={styles.calendarContainer} style={{ height: "700px" }}> <C ...

What is the best way to determine the dimensions of a KonvaJs Stage in order to correctly pass them as the height/width parameters for the toImage function

Currently, I am using KonvaJs version 3.2.4 to work with the toImage function of the Stage Class. It seems that by default, toImage() only captures an image of the visible stage area. This is why there is a need to provide starting coordinates, height, and ...

Execute Cheerio selector once the page has finished loading

Hey there! I'm trying to extract the URL value of an iframe on this website: I've checked the view page source but couldn't find the iframe. Looks like it's loaded after the page is fully loaded through JavaScript. Could it be that ...

The functionality of Jquery Ajax is limited to a single use

I'm having an issue with a page that is supposed to reload the data within a div using jQuery, but it only updates once. Here's a snippet of my code: <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0 ...

Dealing with HTML and Escaping Challenges in jQuery Functions

Here is a string I have: var items = "<div class='item'><div class='item-img' style='background-image: url('images.123.jpg')'></div></div>" I am looking to update the inner HTML of a div: $ ...

Incorporating a dynamic URL within a script tag with AngularJs

I am currently integrating a payment gateway into my Single Page Application (SPA) using AngularJS. The issue I'm facing is that the payment gateway requires me to include a script with a specific ID for the payment process to work correctly. This i ...

Unable to set a value for the variable

const readline = require('readline'); let favoriteFood; const rl = readline.createInterface(process.stdin, process.stdout); rl.question('What is your favorite food?', function(answer) { console.log('Oh, so your favorite food is &a ...

Preventing template rendering in Angular until an event is triggered - but how?

I am currently working on a directive that functions well, but I had to resort to using inline template code in order to delay rendering until the click event occurs. However, I believe it would be more streamlined if I could assign the directive template ...

Using Vue.js 2 on multiple HTML pages with Typescript and ASP.Net Core

My ASP.Net Core MVC project utilizes VueJs2 for more complex tasks, with each view having its own corresponding js file. The directory structure is as follows: ├ Controllers\HomeController.cs (with actions Index & Details) ├ Scripts\Hom ...

Extracting a nested property from a JSON object without relying on the lodash get method

We have a sample json data: { "name": "app", "version": "0.1.0", "description": "Sample description", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "so ...

What strategies can I use to prevent making multiple API calls inside setInterval when initializing a new connection in a socket?

I am currently working on implementing a socket system where users can request a function with an ID, and after receiving the ID, I send requests to an API every second and emit the response back to the user. Issue: Every time a new user requests the same ...

The error message "Property '$store' is not defined on type 'ComponentPublicInstance' when using Vuex 4 with TypeScript" indicates that the property '$store' is not recognized

I'm currently working on a project that involves using TypeScript and Vue with Vuex. I've encountered an error in VSCode that says: Property '$store' does not exist on type 'ComponentPublicInstance<{}, {}, {}, { errors(): any; } ...

What is the reason behind the continual change in the background image on this website?

Can you explain the functionality of this background image? You can find the website here: ...