Steer clear of using the "void" return type and instead utilize an enum subset for better clarity

I am exploring the creation of a type-safe API-SDK where each entity supports different API endpoints based on an enum. In this scenario, only two out of three entities allow posting. Although calling the post-function with a known type resolves correctly, looping over all the endpoints supporting post introduces an unexpected return type void, which poses challenges when passing it to other functions that do not accept void. The example provided below illustrates this issue. How can I eliminate the void return type from the post-function or replace it with never?

enum Entity {
    Person,
    Organization,
    Job,
}

interface Person {}
interface Organization {}

const entityEndpointsSupportingPost= [Entity.Person, Entity.Organization];

type EntityEndpointsSupportingPost = {
    [Entity.Person]: Person;
    [Entity.Organization]: Organization;
    [Entity.Job]: void;
}

async function postEntity<T extends keyof EntityEndpointsSupportingPost>(endpoint: T): Promise<EntityEndpointsSupportingPost[T]> {
    const result = await fetch(`api/${endpoint}`);

    return await result.json();
}

const person = Entity.Person;

// The type here is recognized as Promise<Person>
const postedPerson = postEntity(person);

for (const entity of entityEndpointsSupportingPost) {
    // The type in this loop becomes Promise<void | Person | Organization>
    const postedEntity = postEntity(entity);
}

Answer №1

One issue that stands out in this scenario is the compiler's lack of awareness that entityEndpointsSupportingPost does not include Entity.Job. This is due to the type inference of entityEndpointsSupportingPost as Entity[], which is based on the initializing array literal. While this inference is heuristic and assumes potential future changes to the array, you can provide a hint for a more specific type using a const assertion:

const entityEndpointsSupportingPost =
    [Entity.Person, Entity.Organization] as const;

// const entityEndpointsSupportingPost: // readonly [Entity.Person, Entity.Organization]

This way, the compiler understands that entityEndpointsSupportingPost contains exactly two specified elements with a fixed order.

Subsequently, your code will function correctly without errors because entity is of the union type

Entity.Person | Entity.Organization
, making postEntity(entity) of type
Promise<Person | Organization>
:

for (const entity of entityEndpointsSupportingPost) {
    const postedEntity = postEntity(entity); // okay
    // const postedEntity: Promise<Person | Organization>
}

Furthermore, if you no longer wish to support postEntity(Entity.Job), remove it from EntityEndpointsSupportingPost:

type EntityEndpointsSupportingPost = {
    [Entity.Person]: Person;
    [Entity.Organization]: Organization;
    // [Entity.Job]: void; // removed, unnecessary
}

This adjustment will prompt warnings if postEntity is called with an invalid input, ensuring correct TypeScript coding practices:

postEntity(Entity.Job) // compiler error!

Explore the Playground link to view the 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

Issues with Angular Component not detecting changes in @Input array

I'm dealing with a challenging setup where: The Parent Service (A) is imported in the Parent Component (B). Then, the Parent Component passes an array of Objects to a Child Component (C), which are referenced from the Parent Service (e.g. <child-c ...

Local installation of typings tool

I am looking for a way to install typings 'locally' instead of 'globally.' I prefer not to install jquery typings globally because its version may change in the future, leading to changes in its typings. Although there is the option t ...

Utilizing Rxjs to transform an array of objects

My goal is to map an array of objects. Currently, I have the following code: return this.service.post(url, payload, this.httpOptions) .pipe( map((obj: any, index) => [({ ...obj, val1: obj[index].val1.id, v ...

What is the best way to dynamically load application components based on incoming data?

I am working on an Angular project where I need to dynamically load different components based on API data. The home page consists of a banner and a news section. Instead of having the banner always at the top and the news section at the bottom, I want to ...

What is the best way to create a nullable object field in typescript?

Below is a function that is currently working fine: export const optionsFunc: Function = (token: string) => { const options = { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, } ...

FIREBASE_AUTHCHECK_DEBUG: Error - 'self' is undefined in the debug reference

I'm encountering an issue while trying to implement Firebase Appcheck in my Next.js Typescript project. firebase.ts const fbapp = initializeApp(firebaseConfig); if (process.env.NODE_ENV === "development") { // @ts-ignore self.FIREBASE_ ...

Angular 14 presents an issue where the injectable 'PlatformLocation' requires compilation with the JIT compiler; however, the '@angular/compiler' module is currently missing

I've encountered the following error and have tried multiple solutions, but none of them have been successful: Error: The injectable 'PlatformLocation' requires JIT compilation with '@angular/compiler', which is not available. ...

When I utilize the redux connect function, the class information in my IDE (PhpStorm/WebStorm) seems to disappear

When I use the connect function from redux, it seems to hinder my IDE (PhpStorm) from "Find Usages" on my classes. This is likely because connect returns any, causing the type information from the imported SomeClass file to be lost. export default connect ...

Using Typescript to ensure that objects cannot be added to an array of objects if a specific boolean property is set to true

I've been struggling to figure out how to prevent an object from being added to an array based on a property value in generics. During an online interview, I was given an exercise to create a zoo scenario within an hour: There are five animals in th ...

The optimal location to declare a constructor in Typescript

When it comes to adding properties in an Angular component, the placement of these properties in relation to the constructor function can be a topic of discussion. Is it best to declare them before or after the constructor? Which method is better - Method ...

TypeScript's attempt to replicate Scala's underscore feature has been implemented, but it proves to

I've been working on a personal project for the past 2 years trying to implement Scala's underscore in TypeScript, but haven't been successful. Here is my attempted implementation and its effect. The only thing that I really care about typi ...

When running `npm test`, Mocha TS tests encounter failure, but the issue does not arise when executing them

When running tests in my Typescript nodejs project, I use the following command: mocha --compilers ts:ts-node/register,tsx:ts-node/register The tests run successfully with this command. However, when I try to run them using npm test, I encounter the foll ...

Variables in Angular DI become undefined when a method is called through a passed function reference

Utilizing Angular, I have a class with several Dependency Injection (DI) variables. During normal execution of a method within the class, everything functions as expected and I can access the injected instances of the DI variables. Now, my goal is to crea ...

The requested function imported from the react context is not available on this type

Currently, I am in the process of performing a relatively low-level Typescript migration for my React application. The app utilizes context to define various functions and exports them for external use when necessary. Shown below is my 'DataContext&a ...

Having trouble with importing files from a different folder in a React Typescript project

I have a specific folder arrangement set up https://i.sstatic.net/GFOYv.png My goal is to bring both MessageList.tsx and MessageSent.tsx into my Chat.tsx file // Chat.tsx import React from 'react' import {MessageList, MessageSent} from "./ ...

Error: Unable to retrieve options using this.getOptions function. This issue is unrelated to Vue, it is occurring within

Required Modules "dependencies": { "express": "^4.17.1", "express-static-gzip": "^2.1.1", "react": "^17.0.2", "react-dom": "^17.0.2", "reac ...

What methods does React Router use to extract typed parameters from path strings?

(This question is about understanding functionality, not asking for a step-by-step guide) While using React Router, I noticed that Vscode IntelliSense can offer strongly-typed suggestions when I input parameters in a route like this: <Route path=&apos ...

Function in Typescript that accepts an extended interface as a parameter

I am facing an issue with my interface named "Example" which has a function type called "exampleFunction". The problem arises when this function takes a super class as an input parameter because TypeScript is reporting an error. It states that I cannot use ...

Transmit a sequence of keys to the web browser

I'm having difficulty in sending a Shift key command followed immediately by tilde (~). I've attempted various examples, and here's one that I'm currently working on. I am testing the following scenario - selecting a specific image, t ...

Ensuring robust type safety when using various maps and multiple enums as their keys

I am working on creating a type-safe function for retrieving values from a map. The function needs to handle specific logic in my use case, which is why I require it beyond this simple example below: enum ExampleA { A = 'A' } enum ExampleB { ...