Using Typescript to retrieve the Return Type of a function when called with specific Parameter types

My goal is to create 2 interfaces for accessing the database:

  • dao can be used by both admins and regular users, so each function needs an isAdmin:boolean parameter (e.g.
    updateUser(isAdmin: boolean, returnUser)
    )
  • daoAsAdmin, on the other hand, allows methods to be called without the isAdmin parameter (e.g. updateUser(returnUser))

Below is a snippet of code showcasing this:

type User = { name: string }

type DaoAsAdmin = {
    updateUser<ReturnUser extends boolean>(
        returnUser: ReturnUser
    ): ReturnUser extends true ? User : string
}

type Dao = {
    // injects `isAdmin` as first param of all dao methods
    [K in keyof DaoAsAdmin]: (isAdmin: boolean, ...params: Parameters<DaoAsAdmin[K]>) => ReturnType<DaoAsAdmin[K]>
}

// Real code implementation
const dao: Dao = {
    updateUser(isAdmin, returnUser) {
      throw 'not implemented'
    }
  }

// Using proxy trick to set isAdmin = true 
// as the first param of each dao method
const daoAsAdmin = new Proxy(dao, {
    get(target, prop, receiver) {
        return function (...params) {
            const NEW_PARAMS = [true, ...params]
            return target[prop](NEW_PARAMS)
        }
    },
}) as DaoAsAdmin

// Now, calling updateUser is simplified
const userAsAdmin = daoAsAdmin.updateUser(true) // returns type User
const userStringAsAdmin = daoAsAdmin.updateUser(false) // returns type string
// However, dao functions do not return the expected types
const user = dao.updateUser(false, true) // returns type string | User instead of just User
const userAsStr = dao.updateUser(false, false) // returns type string | User instead of just string

I have attempted various strategies but could not ensure that dao functions return the correct type. It appears that a combination of Parameters and ReturnType is needed, but there are no guidelines on using ReturnType with specified function parameters.

What modifications should I make to the Dao type definition to achieve the desired result?

The actual scenario is more complex and necessitates declaring types and constants separately. Please let me know if further clarification is required.

Typescript playground

Answer №1

Regrettably, TypeScript does not support the manipulation of generic function types at the type level. The language lacks higher kinded types as proposed in microsoft/TypeScript#1213. Even if such features were available, it's unclear how one would go about performing the desired type transformation.

Attempts to utilize conditional types like Parameters<T> or ReturnType<T> on generic functions result in the loss of generics, making it challenging to preserve them while manipulating types.


There is limited capability for modifying generic function types at the value level, demonstrated in microsoft/TypeScript#30125. With this approach, given a generic function type variable gf, another function hof() can be defined such that hof(gf) yields a related generic function type. For example:

function injectIsAdmin<A extends any[], R>(
    f: (...a: A) => R
): (isAdmin: boolean, ...a: A) => R {
    throw 0;
}

The method can be illustrated through an example:

const g = <T extends string, U extends number>(t: T, u: U) => [t, u] as const;

const gi = injectIsAdmin(g);

While helpful, this approach may not scale accordingly for mapped types as shown below:

function mapInjectAsAdmin<A extends Record<keyof R, any[]>, R extends Record<keyof A, any>>(
    f: { [K in keyof A]: (...args: A[K]) => R[K] } & { [K in keyof R]: (...args: A[K]) => R[K] }
): { [K in keyof A]: (isAdmin: boolean, ...args: A[K]) => R[K] {
    throw 0;
}

const badGi = mapInjectAsAdmin({ oops: g });

It becomes necessary to manually define Dao based on DaoAsAdmin by traversing all keys individually. This process involves utilizing a function to achieve the desired type computation, resulting in cumbersome manual coding complexities.

function daoTypeBuilder() {
    // Omitted sketchy code block due to character limit
 return dao;
}

type Dao = ReturnType<typeof daoTypeBuilder>;

Although achieving the required type definition is possible with meticulous effort, it raises questions about its practicality. Ultimately, the task demands convoluted workarounds and trickery rather than straightforward solutions.


In conclusion, accomplishing these manipulations without resorting to undesirable tactics proves to be a challenge within the current constraints of TypeScript.

Playground link to 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

Require type parameter to be of enum type

I have a specific goal in mind: // first.ts export enum First { One, Two, Three } // second.ts export enum Second { One, Two, Three } // factory.ts // For those unfamiliar, Record represents an object with key value pairs type NotWorkingType ...

Utilizing TypeScript interfaces to infer React child props

How can I infer the props of the first child element and enforce them in TypeScript? I've been struggling with generics and haven't been able to get the type inference to work. I want to securely pass component props from a wrapper to the first ...

What is the best way to ensure that a mapped type preserves its data types when accessing a variable?

I am currently working on preserving the types of an object that has string keys and values that can fall into two possible types. Consider this simple example: type Option1 = number type Option2 = string interface Options { readonly [key: string]: Op ...

The discord.js TypeScript is throwing an error stating that the 'index.ts' file is missing when trying to run 'ts-node index.ts'

I have been working on creating a discord bot using discord.js and TypeScript. However, when I attempt to start the bot by running 'ts-node index.ts', I encounter the following error: Error: Cannot find module 'node:events' Require stac ...

Issue with Angular data display in template

My Ionic app with Angular is fetching data in the constructor, but I am facing difficulties displaying it in the HTML. Code component receiver: any; constructor( //.... ) { // get receiver data const receiverData = this.activatedRoute.snapsho ...

Accessing file uploads in Angular 2

<div class="fileUpload btn btn-primary"> <span>Select File</span> <input id="uploadBtn" type="file" class="upload" value="No File Chosen" #uploadBtn/> </div> <input id="uploadFile" placeholder="No File Selected" disable ...

What is the best way to eliminate the left margin entirely in CSS?

I am attempting to create an image view that fully covers the window, without any margins. I have tried various solutions such as setting the body margin and padding to 0, but they do not seem to work. body { margin: 0px; padding: 0px; } or *, html { ...

leveraging parcel for importing typescript dependencies

I am currently using parcel to process typescript for a web extension. I have installed JQuery and its type definitions via npm. In my typescript file, I have the following at the top: import $ from "jquery"; import "bootstrap"; However, when running run ...

Error message in Visual Studio 2017: Identical name 'URLs' declared twice in

In our Visual Studio 2017 project, we have multiple TypeScript files that define a URLs class. Each file contains different implementations of the class to change site URLs based on the specific use case: customer/urls.ts namespace Portal { export cl ...

What causes a standard React component with a default render prop to not pass PropTypes validation successfully?

I'm currently working on a React component with a render-prop that has a generic type. To improve usability, I want to set a default value for the render-prop. The code is functioning correctly, but during type-checking, I encountered a warning regard ...

Tips for retrieving an object from an array with Angular and Firestore

Currently, I am attempting to retrieve an object from Firestore using the uid so that I can return a specific object as a value. I have implemented a function in order to obtain the object 'Banana'. getFruit(fruitUid: string, basketUid: string) ...

Retrieving class properties in typescript

I am working with a class that has numerous methods, which I refer to as myClass. When calling it, the syntax is as follows: myClass[key]() Is there a way to retrieve the valid values for key? I tried using keyof myClass, but received an error message st ...

Typescript constructor that accepts an object as an argument instead of traditional parameters

My constructor is becoming lengthy and not structured the way I would prefer. I am looking to pass an object to my constructor so that I can access fields by their names. Here is how the class looks currently. export class Group { id: string; constru ...

Adding a type declaration to the severity property in React Alert - A guide to Typescript

I've developed a type declaration object for the incoming data, but no matter what I try to define as the type for the property "severity", it's not happy. The options it wants (as displayed below) don't seem feasible. I'm curious if th ...

Guide on associating user IDs with user objects

I am currently working on adding a "pin this profile" functionality to my website. I have successfully gathered an array of user IDs for the profiles I want to pin, but I am facing difficulties with pushing these IDs to the top of the list of profiles. My ...

How to programmatically close an Angular 5 Modal

In my current project, I am working with Angular 5. One of the functionalities I have implemented is a modal window. The HTML structure for this modal looks like this: <div class="add-popup modal fade" #noteModal id="noteModal" tabindex="-1" role="dia ...

Material-UI and TypeScript are having trouble finding a compatible overload for this function call

Currently, I'm in the process of converting a JavaScript component that utilizes Material-ui to TypeScript, and I've encountered an issue. Specifically, when rendering a tile-like image where the component prop was overridden along with an additi ...

What functionality does the --use-npm flag serve in the create-next-app command?

When starting a new NextJS project using the CLI, there's an option to use --use-npm when running the command npx create-next-app. If you run the command without any arguments (in interactive mode), this choice isn't provided. In the documentati ...

Is including takeUntil in every pipe really necessary?

I'm curious whether it's better to use takeUntil in each pipe or just once for the entire process? search = (text$: Observable<string>) => text$.pipe( debounceTime(200), distinctUntilChanged(), filter((term) => term.length >= ...

Acknowledgment Pop-up

When using the PrimeNG table with a custom modal component that I created, I encountered an issue. The edit functionality works correctly and retrieves the correct row id, however, the delete function always returns the id of the first row. dashboard.html ...