Developing a Generic API Invocation Function

I'm currently working on a function that has the capability to call multiple APIs while providing strong typing for each parameter: api - which represents the name of the API, route - the specific route within the 'api', and params - a JSON object containing the required parameters for the chosen API and route.

To better illustrate what I have in mind, I've created an example using the TS Playground here.

The code snippet is as follows:

type APINames = "google" | "yahoo";

type Routes<A extends APINames> = {
    google: "endpoint1" | "endpoint2";
    yahoo: "endpoint3";
}[A];

type RouteParams<A extends APINames, R extends Routes<A>> = {
    google: {
        endpoint1: {
            name: string;
        };
        endpoint2: {
            otherName: string;
        };
    };
    yahoo: {
        endpoint3: {
            otherOtherName: string;
        };
    };
}[A][R];

const execute = <A extends APINames, R extends Routes<A>>(api: A, route: R, params: RouteParams<A, R>) => {
    // ... code
}

execute('google', 'endpoint1', {
  name: 'George Washington'  
});

There seems to be a type error arising from my RouteParams type. The issue lies in the fact that, although [A] serves as a valid first index, [R] does not qualify as a legitimate second index due to the fact that R now holds the type

'endpoint1' | 'endpoint2' | 'endpoint3'
, and certain subsets of those values do not align with all keys present within the RouteParams object. Despite this setback, the provided code does offer useful typing assistance when utilizing the execute function. It's imperative for me to resolve the TypeScript error pertaining to RouteParams. I suspect that there might be a utility type out there capable of addressing this issue, and I am also open to exploring solutions involving completely different structures.

Ultimately, the main objective is to create a function that accepts three arguments: the first being an API name selected from a union type, the second representing a route derived from a union type associated with the specified API, and the third consisting of a JSON parameters object linked to the combination of the API name and route.

Answer №1

To tackle this issue, my strategy would involve separating the object type mentioned in RouteParams and creating a distinct interface for it:

interface ApiMappings {
    google: {
        endpoint1: {
            name: string;
        };
        endpoint2: {
            otherName: string;
        };
    };
    yahoo: {
        endpoint3: {
            otherOtherName: string;
        };
    };
}

Subsequently, I recommend explicitly defining the operation using TypeScript's generics, keyof operator, and indexed access types:

const performOperation = <A extends keyof ApiMappings, R extends keyof ApiMappings[A]>(
    apiType: A, routeName: R, parameters: ApiMappings[A][R]
) => {   // ... code
}

This approach compiles without any errors and retains the desired behavior from the caller's perspective:

performOperation('google', 'endpoint1', { name: 'George Washington' });
performOperation('google', 'endpoint2', { otherName: 'Abraham Lincoln' });
performOperation('yahoo', 'endpoint3', { otherOtherName: 'one of them Roosevelts or something' });

Click here for playground link to view code in action.

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

Accessing the .env file to configure a NestJS application using TypeORM and a custom provider

I am still learning my way around nestJS and I am currently trying to configure .env for an existing application but encountering some issues. Within my custom provider for the appModule, it looks like this: @Module({ providers: [ AbcService, ...

Incorporate a course within the conditional statement

Currently, I'm working on the development of an input site and one of my goals is to highlight empty fields. My plan is to check if a field is empty using an if statement and then apply a specific class that will serve this purpose. This is the JavaS ...

Refreshing the list in a Next.js to-do application post-deletion: a step-by-step guide

I am currently developing a basic to-do application using Next.js, TypeScript, and Axios. I have successfully implemented the delete functionality for tasks, but I am facing an issue with refreshing the tasks list after deletion. I would appreciate any s ...

What is preventing me from binding the array index to a model in AngularJS with two-way binding?

I'm currently facing a challenge with a seemingly simple task: I have an array of string values that I want to link to a select element's options, and then connect the index of the chosen value to a variable. However, I have found that neither us ...

What is the best way to instruct jQuery to disregard an empty server response?

After examining this piece of code: $.ajax({ type: "POST", url: theRightUrl, data: whatToPost, logFunction: whatever, suppressSuccessLogging: !0, dataType: "html" }); I encountered an issue where Firefox displays a "no element ...

Solving the pyramid of doom with Node.js

I have been struggling to find a way to simplify my code structure. Here is what I have come up with so far: router.use('/create', function(res,req,next) { try { var data = { startDate: new Date(req.body. ...

Tabulator: the process of loading an extensive amount of data requires a significant amount of time

Currently, I am encountering an issue with loading data using a tabulator on my webpage. There are 38 tables that need to be populated, each containing approximately 2000 rows of data. The problem lies in the fact that it is taking an excessive amount of t ...

Trouble transferring $rootScope.currentUser between AngularJS profile and settings page

I am in the process of setting up a site using Angular, Express, Node, and Passport. Currently, I am configuring Angular to monitor the $rootScope.currentUser variable with the following code: app.run(function ($rootScope, $location, Auth) { // Watch ...

Adding a custom validation function to the joi.any() method - the easy way!

Is there a way to enhance joi.any() with a new rule that can be applied to any existing type, such as joi.boolean() or joi.string()? I already know how to extend joi by creating a custom type but that doesn't allow me to combine the new type with exis ...

Ways to extract all hyperlinks from a website using puppeteer

Is there a way to utilize puppeteer and a for loop to extract all links present in the source code of a website, including javascript file links? I am looking for a solution that goes beyond extracting links within html tags. This is what I have in mind: a ...

In my Node.js application using Express and Passport, I encountered an issue where res.locals.users is functioning properly but the data it

I'm currently working on an application using NodeJS, Express, and Passport. However, I've encountered an issue when trying to display user data in the views. While I am able to retrieve the ObjectID of the user from res.locals.user, accessing s ...

Guidelines for implementing more rigorous type checks in TypeScript:

I am looking to enhance the code validation process and eliminate any implicit 'any' types. To achieve this, I have set "strict": true in my tsconfig.json. { "compilerOptions": { "target": "ES5", ...

Utilize jQuery to generate an HTML table from a JSON array with rows (Issue: undefined error)

please add an image description hereI am attempting to populate the data in an HTML table using jQuery, but all columns are showing as undefined errors HTML: <table id="example" class="table table-striped" style="width:100%&quo ...

jQuerry's on method fails to respond to newly added elements through the clone() function

I always thought that jquery's on() function would handle events for dynamically added elements in the DOM, such as those added via ajax or cloning. However, I'm finding that it only works for elements already attached to the dom at page load. Cl ...

Trigger Vue to scroll an element into view once it becomes visible

I created a dynamic form that calculates 2 values and displays the result card only after all values are filled and submitted, utilizing the v-if directive. Vuetify is my chosen UI framework for this project. This is the approach I took: <template> ...

JavaScript class name modifications are not functioning as expected

Testing a sprite sheet on a small web page I created. The code for each sprite in sprites.css is structured like this... .a320_0 { top: 0px; left: 0px; width: 60px; height: 64px; background: url("images/sprites.png") no-repeat -787 ...

Cordova's FileReader takes precedence over TypeScript's FileReader functionality

I encountered an issue when adding the cordova-plugin-file-transfer plugin, where I received the error message: reader.addEventListener is not a function. This problem arises due to Cordova FileReader class overriding typescript FileReader. How can this ...

The HTML code may fade away, but the JavaScript is still up and running behind the

Switching between different div elements in my HTML document is a challenge. Here's the code I currently have: <div id="screen1" class="current"> <div id="press_any_key_to_continue"> <font style="font-family: verdana" color="yellow ...

Configuration for secondary dependencies in Tailwind

As per the guidelines outlined in the official documentation, it is recommended to configure Tailwind to scan reusable components for class names when using them across multiple projects: If you’ve created your own set of components styled with Tailwin ...

Incorporating Stripe into your Next.js 13 application: A step-by-step guide

Struggling to incorporate Stripe into my Next.js 13 app for a pre-built checkout form. So far, all attempts have fallen short. Seeking assistance from anyone who has conquered this integration successfully. Any tips or guidance would be highly valued. Pl ...