Maintaining type information while iterating over an object with Typescript

I am faced with the challenge of wrapping functions within an object in order to use their return values, all without altering their signature or losing type information.

// An object containing various functions
const functions = { foo, bar, baz }

// Example wrapper function
const doSomething: (result: any) => void;

// Function to wrap each individual function
function wrapper<P extends any[]>(fn: (...params: P) => any) {
    return (...params: P) => doSomething(fn(...params));
}

// The main wrap function which preserves type information
function wrap(functions) {
    const wrapped = {};

    for (const key in functions) {
        wrapped[key] = wrapper(functions[key]);
    }

    return wrapped;
}

const wrapped = wrap(functions);

// How can we retain type information for wrapped and its functions?

Is it a feasible task to maintain type information (including list of functions and their parameters) while wrapping these functions?

Answer №1

If you're working with TypeScript, you won't get automatic inference for higher-order type operations; you'll need to provide annotations and assertions in certain places. When you check the type of wrapper, you'll see:

/* function wrapper<P extends any[]>(fn: (...params: P) => any): (...params: P) => void */

This means that it returns a function whose parameters mirror those of the input function but has a return type of void. For this scenario, crafting the wrap() function would look something like this:

function wrap<T extends Record<keyof T, (...args: any) => any>>(functions: T) {
    const wrapped = {} as { [K in keyof T]: (...p: Parameters<T[K]>) => void };
    for (const key in functions) {
        wrapped[key] = wrapper(functions[key]);
    }
    return wrapped;
}

The argument functions is of generic type T restricted to an object type with properties as functions. The return wrapped is confirmed to be of a mapped type that mirrors the properties of T, where the function types' return values are changed to void. This setup utilizes the Parameters utility type to specify that the output functions take the same parameters as the input functions.

For thoroughness, here are the sample definitions I'm using:

const functions = {
    foo: (x: string) => x.length,
    bar: (x: number) => x.toFixed(2),
    baz: (x: boolean, y: boolean) => x && y;
}

const doSomething = (result: any) => { console.log(result) };

Upon calling wrap(), we observe the following outcomes:

const wrapped = wrap(functions);
wrapped.foo("hey"); // 3
wrapped.bar(Math.PI); // 3.14
wrapped.baz(true, false); // false

If there's an incorrect usage, the compiler will raise errors, such as:

wrapped.foo(true); // error! boolean is not a string
wrapped.foo("string", false); // error! expected 1 argument, received 2

Seems like it aligns with what you're looking for, right?

Visit the Playground for Interactive Coding

Answer №2

Thanks to the guidance provided by jcalz's solution, I was able to create a more versatile function objects mapper.

This function wraps each function within an object with a specified function, while preserving the original type signature of the object.

function mapFunctionsObj<T extends Record<keyof T, (...p: any[]) => any>>(
    fn: <X>(result: X) => X,
    functions: T
) {
    const mapped = {} as any;
    for (const key in functions) {
        mapped[key] = (...params) => fn(functions[key](...params));
    }
    return mapped as { [K in keyof T]: (...p: Parameters<T[K]>) => ReturnType<T[K]> };
}

For further insight into the typings used, please refer to the original answer.

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

Encountering the issue "ReferenceError: Unable to reach 'Base' before initialization" while testing a Next.js web application on a local server

Below is the content of the complete index.tsx file: import { Base } from '../templates/Base'; const Index = () => <Base />; export default Index; I have researched other posts regarding this error message, but none resemble the struc ...

Issues have been reported regarding the paramMap item consistently returning null when working with Angular 8 routing

I am encountering an issue with Angular 8 where I am trying to fetch some parameters or data from the route but consistently getting empty values. The component resides within a lazy-loaded module called 'message'. app-routing.module.ts: ... { ...

Using TypeScript to define task invocation parameters with AWS CDK's CfnMaintenanceWindowTask

Currently, I am utilizing AWS CDK along with the library @aws-cdk/aws-ssm and TypeScript to construct CfnMaintenanceWindowTask. The code example I am working on is derived from AWS CloudFormation documentation, specifically for "Create a Run Command t ...

Is there a way to specifically target the MUI paper component within the select style without relying on the SX props?

I have been experimenting with styling the Select MUI component using the styled function. I am looking to create a reusable style and move away from using sx. Despite trying various methods, I am struggling to identify the correct class in order to direct ...

What benefits does Observable provide compared to a standard Array?

In my experience with Angular, I have utilized Observables in the state layer to manage and distribute app data across different components. I believed that by using observables, the data would automatically update in the template whenever it changed, elim ...

The validation for the email field in Bootstrap, specifically in Angular 5, is not functioning properly for the "Email is Required

As a newcomer to Angular, I am seeking assistance with validation in my Angular 5 application. Specifically, I need to validate user emails and only allow navigation to a new component when a valid email is entered upon clicking a button. While pattern va ...

What are the steps to view output on VS Code using Typescript?

Currently, I am working on code challenges using Typescript in Visual Studio Code. However, whenever I attempt to run the code and view the output, I encounter an error stating "Code Language is not supported or defined". I have already set the language ...

Attempt the HTTP request again only for specific status codes

When developing my Angular application, I encountered a situation where I needed to make an HTTP call to a backend server. To enhance its reliability, I decided to incorporate an interceptor to implement the retry pattern. In the past, I utilized RxJS&apo ...

Encountering difficulty retrieving host component within a directive while working with Angular 12

After upgrading our project from Angular 8 to Angular 12, I've been facing an issue with accessing the host component reference in the directive. Here is the original Angular 8 directive code: export class CardNumberMaskingDirective implements OnInit ...

Recording attributes in a React component with Typescript

Is there a way to efficiently document React component props so that the comments are visible in the component's documentation? For example, consider this component: type TableProps = { /** An array of objects with name and key properties */ colu ...

Struggling to update TypeScript and encountering the error message "Unable to establish the authenticity of host 'github.com (192.30.253.113)'"

While attempting to update my version of TypeScript using npm, I ran into an issue when trying to execute the following command: localhost:Pastebin davea$ npm install typescript/2.8.4 --save-dev The authenticity of host 'github.com (192.30.253.113)&a ...

Despite EsLint and Prettier's efforts to improve code quality, users are experiencing frequent unnecessary errors and unexpected auto-insertion of parentheses at

When working with redux saga for handling asynchronous calls, I encountered an issue. After making an API call and storing the retrieved data in local storage, eslint/prettier automatically adds parentheses to the assignment operator at the end of the line ...

implementing firestore onsnapshotListner feature with pagination

I have a web application that needs real-time updates on a large collection of documents. However, due to the size of the collection, retrieving data without applying a limit is not feasible and inefficient. Therefore, it is crucial to implement a query li ...

Guide to encapsulating a container within a map function using a condition in JSX and TypeScript

Currently, I am working with an array of objects that are being processed by a .map() function. Within this process, I have a specific condition in mind - if the index of the object is greater than 1, it should be enclosed within a div element with a parti ...

Unveiling individual modules of an Angular library using public-api.ts in the latest version of Angular (Angular 13)

After completing an upgrade on my Angular library project from version 11 to 13, I encountered an issue when attempting to execute the ng build command. In version 11, the setup looked like this: I had multiple smaller modules, each containing various co ...

Retrieving an Observable within an Observable is a feature found in Angular 9

Seeking a solution to return an Observable nested within another Observable. Although I've tried using the pipe and map operators, it doesn't appear to be functioning correctly for me. What could be causing the issue? My development environment ...

Adding an object with a composite key to an IndexedDB object store is not permitted as the key already exists within the store. This limitation occurs when attempting to add an entry

I am facing an issue with my objectStore where adding an object with the same productId but a different shopName triggers an error in the transaction showing: ConstraintError: Key already exists in the object store.. This is how I initialized the objectSto ...

The data type does not match the expected type 'GetVerificationKey' in the context of express-jwt when using auth0

I am in the process of implementing auth0 as described here, using a combination of express-jwt and jwks-rsa. However, I encountered an error like the one below and it's causing issues with finishing tsc properly. Error:(102, 5) TS2322: Type 'S ...

Unable to complete plugin registration due to a "handler missing or undefined" error

I'm in the process of developing a sample nodejs application that includes a plugin. However, when I attempt to run the application, I encounter an error stating "Missing or undefined handler". Here is my plugin file: exports.plugin = { name: "t ...

At what point do we employ providers within Angular 2?

In the Angular 2 documentation, they provide examples that also use HTTP for communication. import { HTTP_PROVIDERS } from '@angular/http'; import { HeroService } from './hero.service'; @Component({ selector: 'my-toh&ap ...