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

When attempting to access /test.html, Node.js server returns a "Cannot GET"

Embarking on the journey of diving into Pro AngularJS, I have reached a point where setting up the development environment is crucial. This involves creating an 'angularjs' directory and placing a 'test.html' file in it. Additionally, o ...

Webpack2 now transforms sass/scss files into JavaScript rather than CSS during compilation

I've created a webpack script to compile all .scss files into one css file. I decided to use webpack instead of gulp or grunt for simplicity, as it can be configured in a single file. However, I am encountering an issue where scss files are being com ...

Evaluating TypeError in CoffeeScript using Jasmine with Backbone.js

Currently, I am following the PeepCode video tutorial on Backbone.js, but I am rewriting all the code in CoffeeScript instead of plain JavaScript. Everything is going well so far, except when I attempt to run Jasmine tests on the code, I encounter some Ty ...

Turn off Appbar padding at the top and bottom

I am looking to create a customized box within the Appbar that spans the full height, as illustrated in the image below: https://i.stack.imgur.com/CFMo0.jpg However, the default Appbar provides padding from all sides to its internal elements, leading to ...

In JavaScript, the mousedown event consistently receives the "e" parameter as the event object

I am facing an issue while trying to handle a middle mouse button click event using JQuery on a DataTable from the website https://datatables.net/. Below is the code I have implemented. var tbl = document.getElementById("entries"); $(tbl).on('mousedo ...

Utilizing variables to set the templateUrl in Angular2

Trying to assign a variable to the templateUrl in my component, but it's not functioning as expected. @Component({ selector: 'article', templateUrl: '{{article.html}}', styleUrls: ['styles/stylesheets/article.comp ...

Tips for adjusting image size to take up only half of the screen in NextJS

Struggling to resize an image to fit only 50% of the screen in NextJS? The Image component provided by NextJS comes with its own inline styling, making it tricky to customize. Currently, I attempt to style the image by wrapping the Image component in a spa ...

JavaScript-powered dynamic dropdown form

I need help creating a dynamic drop-down form using JavaScript. The idea is to allow users to select the type of question they want to ask and then provide the necessary information based on their selection. For example, if they choose "Multiple Choice", t ...

Guide to importing Bootstrap 5 bundle js using npm

Having some issues with implementing Bootstrap5 and NPM. The website design is using bootstrap, which works fine, but not all the JavaScript components (dropdowns, modals, etc). I want to figure out how to import the Bootstrap JS bundle without relying on ...

The specified type 'Observable<{}' cannot be assigned to the type 'Observable<HttpEvent<any>>'

After successfully migrating from angular 4 to angular 5, I encountered an error in my interceptor related to token refreshing. The code snippet below showcases how I intercept all requests and handle token refreshing upon receiving a 401 error: import { ...

Issues with getOptionLabel in Material UI

I have a constant with the following values : const Reference = [ { label: "RF-100", year: "Test" }, { label: "RF-200", year: "Test2" }, { label: "RF-300", year: "Test3" }, ]; and my Autoco ...

Eliminate unnecessary CSS classes from libraries such as bootstrap when working on a React project

Our team is currently in the process of developing a React project that involves webpack and babel. Our goal is to automatically remove any unused CSS classes from CSS frameworks Bootstrap and AdminLTE 2, which are integral parts of our project. For this ...

What's the method for validating the spread operator in Typescript?

Is it possible to prevent this code from compiling? (since it currently does...) const bar: { a: string } = { a: '', ...{b: ''} } ...

Using AJAX/JQuery along with PHP to instantly send notifications without refreshing the page for a contact form

Website URL: This website was created by following two separate tutorials, one for PHP and the other for AJAX. The main objective was to develop a contact form that validates input fields for errors. If no errors are found, a success message is displayed ...

JavaScript does not recognize the $ symbol

Firebug is indicating that there is an issue with the $ variable not being defined. I have a basic index.php page that uses a php include to bring in the necessary content. The specific content causing the error is shown below: <script type="text/jav ...

The logical operator malfunctions following a computation

var sub_response_type = {"survey_question":["Test lable"],"responseTypeText":"Exit label","select_param_type":[">","<"],"questions_id":["7","8"],"select_param_value":["12","34"],"radio_type":["&&"]}; var order = ['questions_id' ...

Fetching data in a post request seems to be causing an issue with FormData image being

I've implemented a profile picture file upload system with the following HTML: <form enctype="multipart/form-data" id="imageUpload" > <img id="profileImage" src="./images/avatar.png& ...

Efficient methods to reach the desired result using Selenium WebDriver promises

After working on a piece of code that utilizes Selenium WebDriver to retrieve the text of an element, I am wondering if there is a more concise way to accomplish this task? async function getText(driver, locator) { return await (await driver.findEleme ...

The functionality of jQuery.hover with AJAX is malfunctioning

I am facing an issue with my jquery hover combined with $.post method. My initial intention was to create a series of select buttons where hovering would trigger a change in the image being displayed (the image path would be loaded via $.post). The image ...

Extracting values from an event in Vue.js: A step-by-step guide

When working with Vue.js, I use the following code to fire an event: this.$emit("change", this.data); The parent component then receives this data, which is in the form of an object containing values and an observer. It looks like this: { data ...