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

Where can I locate htmlWebpackPlugin.options.title in a Vue CLI 3 project or how can I configure it?

After creating my webpage using vue cli 3, I decided to add a title. Upon examining the public/index.html file, I discovered the code snippet <title><%= htmlWebpackPlugin.options.title %></title>. Can you guide me on how to change and cu ...

Deleting sections of a string using JavaScript

I've encountered a rather unique issue where I need to address a bug on a website. The problem is that when a string is generated dynamically, it automatically adds 5 spaces before and after the string. Ideally, this should be fixed in the backend cod ...

Microphone Malfunction: Abrupt End of Input Detected

I have been experimenting with SpeechRecognition to incorporate microphone functionality into one of my projects. However, when I check the Chrome Console, it displays the error message: Unexpected end of input const speechRecognition = window.webkitS ...

Ensuring form accuracy upon submission in AngularJS 1.5: Understanding the $invalid state in relation to $pristine field

When loading data in a form, I want to allow the user to submit data only if the form is valid. Initially, the form is pristine but invalid. If the user edits any one of the three fields, the form is no longer pristine, which is acceptable. However, the ...

Using jQuery Ajax to Send Values Between Functions and Replace Nulls

I need assistance with handling the value from a dropdownlist in my JavaScript function. The function works well if the value is not Null, but I want to replace a Null value with the static value "asc" before passing it to the next function. function g ...

The 'import type' declaration cannot be parsed by the Babel parser

Whenever I attempt to utilize parser.parse("import type {Element} from 'react-devtools-shared/src/frontend/types';", {sourceType: "unambiguous"}); for parsing the statement, I come across an error stating Unexpected token, exp ...

Guide to automating tests on MAC using Protractor with Appium and IOS-Simulator

Is there a way to run automated tests using Protractor (or WebDriverJS) on a MAC with Appium and the IOS Simulator? We have been unsuccessful in running tests with the following configuration file, although it works fine with Selenium 2.0 - WebDriver. He ...

What is the best way to kickstart a Reactive Angular 2 form by utilizing an Observable?

My current strategy involves storing the form values in my ngrx store so that users can easily navigate around the site and return to the form if needed. The concept is to have the form values repopulate from the store using an observable. This is how I a ...

Error: An issue occurred with the tasks in the Gruntfile.js file

pm WARN EPACKAGEJSON <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="74041506001a1106041b0600151834445a445a44">[email protected]</a> No description npm WARN EPACKAGEJSON <a href="/cdn-cgi/l/email-protection" ...

Incorporate image into Vue.js form along with other information

I have been successfully sending the content of multiple fields in a form to the Database. Now I am looking to add an additional field for uploading images/files and including it with the other form fields, but I am unsure about how to accomplish this task ...

How to efficiently retrieve a form's data from multiple forms with identical ids in JavaScript

I am facing a challenge with multiple forms on the html page that have the same ID. I need to send each form's information to the server based on user selection, but I haven't been able to find a relevant solution. For my Authorization polic ...

Removing repetitive strings from an array in the most efficient manner

We've successfully developed a script to eliminate duplicate strings from an array while preserving the order necessary for angular's ng-repeat loop. It's also important that the remaining elements maintain their original index. scope.feedb ...

Using `v-if` with a Vuex getter

Currently, I am utilizing a vuex getters called isLoggedIn to verify whether a user is logged in or not. <div v-if="isLoggedIn" class="ml-2 py-2 group relative">...</div> data() { return { isLoggedIn: this.$store. ...

Using optional function arguments with destructured arguments in TypeScript can result in throwing an error

interface Type1 { attr1: string; attr2: string; } interface Type2 { attr1: string; attr2: string; attr3: string; // additional attribute } function fn(config: Type1 | Type2): void { // The error code is displayed above. I am ...

Unable to fetch local file using ajax from a local HTML page

According to Same Origin Policy, the SOP should not be applied to the file:// protocol. However, I'm having trouble with my code. I am running a testing page from my local system, and both when I try to access abc.txt in the same directory as the HTML ...

What is the best way to retrieve JSON data in a React application?

useEffect(async () => { const fetchPostData = async () => { const response = await axios("") setPosts(response.data) } fetchPostData(); }, []) Rendering : posts.map(post => <li>{post.name} ...

Unwrapping Promises in Angular for Seamless Resolution

I recently started working with Angular and found myself in a large project. I encountered a simplified version of my code below: var beforeClose = function() { var closeDeferred = $q.defer(), a = $q.defer(), b = $q.defer(), c = $q.defer() ...

Trouble in sending email with attachment using Microsoft Graph

I have been working on an application that has the functionality to send emails from a User, following the guidelines provided in this article. Despite everything else functioning correctly, I'm facing an issue when trying to include attachments. The ...

The function cannot be called on params.getValue()

I am facing an issue while trying to retrieve the value of a specific row's column ID. The goal is to change the URL by appending the retrieved ID when clicking on an icon. However, I encountered an error stating TypeError: params.getValue is not a fu ...

React Big Calendar encountered an error: The element type provided is not valid, as it is expected to be a string for built-in

Error One: The element type is invalid: it was expecting a string (for built-in components) or a class/function (for composite components), but received undefined. This could be due to not exporting your component correctly from the file where it's d ...