Creating type definitions for recursive functions in TypeScript

I've created a JavaScript function that continuously accepts functions(params => string) until it receives an object, at which point it resolves the final string as a concatenation of all the functions invoked over the same object passed in at the end.

Here is the function:

export function prefixer(func1?: any) {

    function identity(x: any) { return x; }

    function curriedPrefixer(fns: any[]) {

        return function next(param: any) {

            if (typeof param == 'function') {
                return curriedPrefixer(
                    fns.concat(param)
                );
            }

            if (typeof param == 'object')
                return fns.reduce(
                    (prev, next) => prev + next(param), ''
                );

            return undefined;
        }
    }

    return curriedPrefixer([func1 || identity])
}

My challenge lies in defining the appropriate types for its parameters and return type so that users of this function can pass a generic type (to inform the function about the type of the final params object) and effectively use the output repeatedly.

Below is a test case with the function:

test('should auto prefix', () => {

    let prefix1: any = prefixer((params: any) => `https://wwww.${params.domain}.com`)
    let prefix2: any = prefix1(() => '/path/to/item')
    let prefix3: any = prefix2((params: any) => `/${params.itemId}`)

    let params = {
        domain: 'google',
        itemId: '5444'
    }

    let resolvedString1 = prefix1(params);
    let resolvedString2 = prefix2(params);
    let resolvedString3 = prefix3(params);

    let trueResult1 = `https://wwww.${params.domain}.com`
    let trueResult2 = `https://wwww.${params.domain}.com/path/to/item`
    let trueResult3 = `https://wwww.${params.domain}.com/path/to/item/${params.itemId}`

    expect(resolvedString1).toEqual(trueResult1);
    expect(resolvedString2).toEqual(trueResult2);
    expect(resolvedString3).toEqual(trueResult3);
});

I have attempted different ideas without success and haven't found a satisfactory answer on recursive functions in TypeScript. Here's one attempt I made which didn't resolve the types definition:

export function prefixer<T>(func1?: any) {

    function identity(x: any) { return x; }

    function curriedPrefixer<M>(fns: any[]) {

        return function next<S>(param: S | M | ((p: S | M) => any)) {

            if (typeof param == 'function') {
                return curriedPrefixer(
                    fns.concat(param)
                );
            }

            if (typeof param == 'object')
                return fns.reduce(
                    (prev, next) => prev + next(param), ''
                );

            return undefined;
        }
    }

    return curriedPrefixer<T>([func1 || identity])
}

// Still need to pass (p: any)...
let prefix1 = prefixer<{ domain: string }>((p: any) => `https://wwww.${p.domain}.com`)
let prefix2 = prefix1<{ itemId: string }>((p: any) => `https://wwww.${p.itemId}.com`)

Answer №1

To begin typing this, start by defining the type signature of the prefixer function without specifying the implementation. The structure should resemble the following:

// Helper types for brevity in subsequent definitions
type Pojo = Record<string, unknown>
type EmptyPojo = Record<never, unknown>

// TData represents the expected object shape for all previous prefixer functions passed to this one
interface CurriedPrefixer<TData extends Pojo> {
  // First overload: when passing a function to the curried prefixer
  // T denotes the fields expected by the new prefixer function being passed
  <T extends Pojo = EmptyPojo>(
    // Consider changing the type of `data` to `TData & T` instead of just `T`
    // to grant access to data from all previous prefixer functions,
    // though this approach is less secure and explicit typing per iteration is recommended
    prefixerFn: (data: T) => string
  ): CurriedPrefixer<TData & T>

  // Second overload: when passing an object directly
  (data: TData): string
}

Now, you can validate that this type signature behaves as expected, while overlooking the actual implementation momentarily: sandbox link

The next step involves aligning the implementation with the established type signature. A possible implementation might resemble the following example: sandbox link

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

Tips for including HTML content in an agm-marker using Angular 2

In my Angular 2 application using agm map for vehicle tracking, I am looking for a way to customize the map marker. Specifically, I want to display the vehicle status by changing the color of the marker (green for running, red for stopped, yellow for idle) ...

What could be causing the undefined properties of my input variables in Angular?

Currently, I am fetching data from a service within the app component and passing it down to a child component using @Input. Oddly enough, when I log the data in ngOnInit, it appears correctly in the child component. However, when I try to assign it to a v ...

Encountering a Typescript Type error when attempting to include a new custom property 'tab' within the 'Typography' component in a Material UI theme

Currently, I am encountering a Typescript Type error when attempting to add a custom new property called 'tab' inside 'Typography' in my Material UI Theme. The error message states: Property 'tab' does not exist on type &apos ...

Sequelize is not giving the expected model even after utilizing `include: [{ model: this.buildModel, required: true }]`

I've hit a roadblock trying to solve this problem. Despite my belief that I've correctly handled the migration, model definition, and query, I'm unable to retrieve the build in my iteration. Below are the models: SequelizeBuildModel.ts @Ta ...

Is error propagation from nested Promise to parent Promise not working properly in Node.js?

I'm currently working on a Node.js/TypeScript API with Express. Below is a snippet from my get method where I encountered an error in the format function. The error is caught by the promise, but it doesn't propagate to the parent promise after th ...

What is the best way to find a partial string match within an array of objects when using Jest?

I am currently utilizing the following versions: Node.js: 9.8.0 Jest: 22.4.2 A function called myFunction is returning an array structured like this: [ ... { id: 00000000, path: "www.someUrl.com/some/path/to" } ... ] I ...

The type '(props: Props) => Element' cannot be assigned to the type 'FunctionComponent<FieldRenderProps<any, HTMLElement>>' in React-final-form

I'm fairly new to using TypeScript, and I am currently working on developing a signUp form with the help of React-Final-Form along with TypeScript. Here is the code snippet that describes my form: import React from "react"; import Button from "@mater ...

JavaScript Definition File for TypeScript

Within my repertoire is a Js File, comprised of a leaflet plugin, Js: L.BingLayer = L.TileLayer.extend({ options: { subdomains: [0, 1, 2, 3], type: 'Aerial', attribution: 'Bing', culture: '' }, initialize ...

What is the correct way to configure the environment variables for the vscode plugin?

After attempting to set it using cross-env, the variable remained undefined following execution in VSCode. What steps can I take to resolve this issue? https://i.sstatic.net/bKYLe.png ...

Node.js - Creating seamless integration between Sequelize model JS and controller TS

Having trouble making my User.js model recognized inside my UserController.ts with sequelize in TypeScript. Edit: Unable to change the file extensions for these files. In the await User.findAll() part, an error occurs when running on the server, stating ...

The tslint exclusion is not functioning properly for tsoa endpoints

I'm trying to remove the routes.ts file generated by tsoa routes from being compiled by tslint. I've used the exclude option but it doesn't seem to be working specifically for routes.ts. The exclude option works for other files, except for r ...

What is the best method to retrieve the nested value in this JavaScript/HTML code?

Can anyone help me extract the value "Yes, I am a Speaker" from this code using Javascript DOM element with getElementById? The challenge is that the value is nested inside a list element without any specific attributes. Each block of code has a unique l ...

Are there any restrictions on the amount of data that can be included in a Sankey diagram created from an Excel sheet? I would

[please provide a description of the image][1]I am encountering an issue with data limitation in plotting a Sankey diagram from an Excel sheet. I have imported an Excel sheet with 1300 rows of data, but I am only able to plot 12 rows of data. Can anyone pl ...

"webpack" compared to "webpack --watch" produces varying results in terms of output

My project is built on top of this setup: https://www.typescriptlang.org/docs/handbook/react-&-webpack.html Running webpack compiles a bundle that functions correctly in the browser. However, running webpack --watch to recompile on file changes resul ...

Keep the code running in JavaScript even in the presence of TypeScript errors

While working with create-react-app and typescript, I prefer for javascript execution not to be stopped if a typescript error is detected. Instead, I would like to receive a warning in the console without interrupting the UI. Is it feasible to adjust the ...

Ways to conditionally display a component in Next.js without the issue of caching CSS styles

I'm a newcomer to Next.js and I'm still trying to wrap my head around how the caching works. Let's take a look at this simplified example: An index page that displays either Test1 or Test2 components, based on whether the current minute is ...

What is the best way to hold out for a specific number of promises to be fulfilled and halt the resolution of any others

While working in TypeScript, I need to create around 100 instances of Promise. However, I am only interested in waiting for the resolution of 5 of them. Any promises beyond that can either be canceled (if feasible) or rejected since they are no longer requ ...

Navigating a SwipeableDrawer in React with scrolling functionality

I'm currently using a swipeable drawer in React from MUI to display lengthy content. My goal is to keep the title visible even when the drawer is closed, and I was able to achieve this through the following method: MUI SwipeableDrawer I've provi ...

Is it possible to use Firebase auth.user in order to retrieve the signed-in user directly?

As I develop a webapp with NextJS v13.4 and firebase as my backend using the firebase web modular api, I came across a statement in the documentation: "The recommended way to get the current user is by setting an observer on the Auth object." ...

Selecting the optimal data structure: weighing the benefits of using boolean check versus array .include (balancing performance and redundancy

My objects can have one or more properties assigned, with a total of 5 different properties in my case. To illustrate this, let's use a simple movie example where each movie can be assigned from 5 different genres. I have come up with two methods to ...