TypeScript definition for a dispatcher within a map

I am in the process of creating a typed function that will utilize an object based on its key:

const commandOrQuery = {
  CREATE_USER_WITH_PASSWORD: CreateUserCommandHandler,
  GET_USERS: GetUsersQueryHandler,
};

The structure of commandOrQuery is as follows: Record<string, Function>

I intend to develop a dispatch function that functions like this:

export const dispatch = ({type, args}) => commandOrQuery[type](args)

Here's how it works: type represents the key from the commandOrQuery object. args correspond to the arguments of the commandOrquery[type] function. The dispatch function then returns the type of the object.

For example, when I execute the following code:

const result = await dispatch({
  type: "GET_USERS",
  args : { 
    // tenantId is exclusive to "GET_USERS"
    tenantId: string;
  }
});

// The result would have the shape {name: string, id: string}[] specific to "GET_USERS" and GetUsersQueryHandler

I've made progress by defining the types separately like so:

export type Mediator =
  | {
      type: "CREATE_USER_WITH_PASSWORD";
      arg: CreateUserCommand | typeof CreateUserCommandHandler;
    }
  | {
      type: "GET_USERS";
      arg: GetUsersQuery | typeof GetUsersQueryHandler;
    }
  | {
      type: "GET_USER_PROFILE";
      arg: GetUserProfile | typeof GetUserProfileQueryHandler;
    };

And adjusting the dispatch implementation accordingly:

export const dispatch = ({type, args}: Mediator) => commandOrQuery[type](args)

However, the missing piece right now is the return type. My goal is for TypeScript to automatically infer the ReturnType once I provide the type in the argument.

Is there a way to achieve this?

I've been exploring various resources for a couple of hours now: TypeScript conditional return value type? Typescript Key-Value relation preserving Object.entries type

Edit x1: Check out the Code Sandbox demo here: https://codesandbox.io/s/prod-http-50m9zk?file=/src/mediator.ts

Edit x2:

Let's consider the following file structure:

https://i.sstatic.net/vAS2g.png

//file: bounded_contexts/CreateUserWithPasswordCommand.ts 
export type CreateUserCommand = {
  email: string;
  password: string;
};

export async function CreateUserCommandHandler(cmd: CreateUserCommand) {
  console.log("Creating User");
  return true;
}
// file: bounded_contexts/GetUserProfileQuery.ts
export type GetUserProfileQuery = {
  userId: string;
};

export async function GetUserProfileQueryHandler(query: GetUserProfileQuery) {
  console.log("Searching in db the userId", query.userId);
  return {
    firstName: "Carlos",
    lastName: "Johnson",
    dateOfBirth: new Date()
  };
}
// file: bounded_contexts/GetUsersQuery.ts
export type GetUsersQuery = {};

export async function GetUsersQueryHandler(q: GetUsersQuery) {
  return {
    users: [
      {
        id: "id-1",
        name: "Julian Perez"
      },
      {
        id: "id-2",
        name: "Adam Smith"
      }
    ]
  };
}

// file: index.ts
import { dispatch } from "./mediator";

(async () => {
  // Result should be boolean
  const result = await dispatch({
    type: "CREATE_USER_WITH_PASSWORD",
    arg: {
      email: "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="22564a47474f434b4e624f434b4e0c414d4f">[email protected]</a>",
      password: "the password"
    }
  });

  // result2 should be { users: {name: string; id: string }[]
  const result2 = await dispatch({
    type: "GET_USERS",
    arg: {}
  });
  // resul3 should be { firstName: string; lastName: string; dateOfBirth: Date}
  const result3 = await dispatch({
    type: "GET_USER_PROFILE",
    arg: {
      userId: "the user Id"
    }
  });
})();

// file: mediator.ts
import {
  CreateUserCommandHandler,
  CreateUserCommand
} from "./bounded_contexts/CreateUserWithPasswordCommand";
import {
  GetUsersQueryHandler,
  GetUsersQuery
} from "./bounded_contexts/GetUsersQuery";
import {
  GetUserProfileQueryHandler,
  GetUserProfileQuery
} from "./bounded_contexts/GetUserProfileQuery";

const commandsOrQueries = {
  CREATE_USER_WITH_PASSWORD: CreateUserCommandHandler,
  GET_USERS: GetUsersQueryHandler,
  GET_USER_PROFILE: GetUserProfileQueryHandler
};

type Mediator =
  | {
      type: "CREATE_USER_WITH_PASSWORD";
      arg: CreateUserCommand | typeof CreateUserCommandHandler;
    }
  | {
      type: "GET_USERS";
      arg: GetUsersQuery | typeof GetUsersQueryHandler;
    }
  | {
      type: "GET_USER_PROFILE";
      arg: GetUserProfileQuery | typeof GetUserProfileQueryHandler;
    };

export function dispatch({ arg, type }: Mediator) {
  return commandsOrQueries[type](arg as any);
}

Answer №1

`, lies a coding challenge discussing the issue with a particular function implementation. The problem presented is that the compiler struggles to identify the relationship between `commandsOrQueries[type]` and `arg`, despite `Mediator` being a discriminated union type. To address this, it is suggested to transform the function into a generic one with a key-like type parameter. This allows for the representation of the correlation between input and output using mapped types on this parameter. By assigning a dummy variable name to `commandsOrQueries` and performing type manipulation operations, a solution is proposed. This involves defining various key types such as `Commands`, `InputMap`, and `OutputMap`. With these in place, the compiler can now recognize `commandsOrQueries[type]` as a function operating on specific keys within the original union. The process further extends to defining the `Mediator` type as a distributive object type per the referenced Microsoft TypeScript issue. This generic approach enables specifying the desired union member while defaulting to the full union if necessary. With these adjustments made, the function `dispatch` can be successfully defined and implemented. By leveraging the newfound type clarity, compiling without errors becomes achievable, signifying a successful resolution to the initial problem. A practical test scenario is also provided, showcasing how the modified setup handles different types of dispatch calls efficiently, with the correct typing confirmed by the compiler. For those interested in experimenting with the code firsthand, a link to the TypeScript Playground is conveniently included at the end of this detailed explanation.

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

Using TypeScript: Inclusion of all object keys that correspond to a particular type

When working with an object type (or class type), I am looking to create a function that will take the object and a list of its keys as parameters. However, I specifically want to restrict the keys to only those that are mapped to a value of a certain type ...

Storing the subscription value retrieved from an API in a global variable

I am trying to find a way to make the data retrieved from an API accessible as a global variable in Typescript. I know that using subscribe() prevents this, so I'm looking for a workaround. Here is the API code: getResultCount(category:any):Obs ...

Design a personalized hook in React using Typescript that doesn't require the use of props

Recently delving into the world of React and Typescript, I've come across a common dilemma regarding typing props and creating custom hooks without the need to pass props. Let's take an example: import { useState, useEffect } from 'react&apo ...

What is the process of integrating SCSS into an Angular2 Seed Project?

Is there a recommended method for incorporating SCSS into an Angular2 Seed Project? Are there any informative tutorials or reference sites available? I attempted to implement SCSS following instructions from this link https://github.com/AngularClass/angu ...

Top Method for Dynamically Adding Angular Component on Click Action

As I develop my website, I am faced with the challenge of incorporating multiple components. One specific feature involves allowing users to dynamically add a component of their choice to the "Home" page when they click a button. I have explored three diff ...

Retrieve the values of a dynamic JSON object and convert them into a comma-separated string using Typescript

I recently encountered a dynamic JSON object: { "SMSPhone": [ "SMS Phone Number is not valid" ], "VoicePhone": [ "Voice Phone Number is not valid" ] } My goal is to extract the va ...

Techniques for a versatile class limited to a particular category

In my code, I have a Vector class that looks like this: class Vector<N extends number> {...} N represents the size or dimension of the vector. This Vector class also includes a cross product method to calculate the cross product between vectors: cro ...

In what manner can I retrieve this particular type?

Can you provide me with an example of how to use this type? interface MyCode{ (): Function; title: string; } I've been thinking about various possibilities but haven't been able to figure it out. One approach is: let testCode: MyCode = ...

Utilizing trackingjs as an external library in Ionic2

I have been attempting to incorporate the trackingjs library (https://www.npmjs.com/package/tracking) into my ionic2 project. Following the guidelines in the documentation (https://ionicframework.com/docs/v2/resources/third-party-libs/), I was able to suc ...

Declaring UMD module in TypeScript: Generating typings for a UMD module authored in TypeScript

For the purpose of learning, I created a small TypeScript library and utilized webpack to generate a JS UMD module from my TypeScript code. Here is the structure of my project: |-dist |-js |-my-lib.min.js // Minified library as UMD module | ...

What is the method for updating a 'Signal' within an 'Effect'?

Working with signals in Angular 17, I encountered an issue while trying to update the value of a signal. The error message that I received is as follows: NG0600: Writing to signals is not allowed in a `computed` or an `effect` by default. Use `allowSignalW ...

In an array where the first 3 images have been filtered using an if statement, how can I show the image at the 3rd index (4th image) starting from the beginning?

In my personal web development project, I'm using AngularJS for the front-end and .NET Core for the back-end to create a website for a musical instrument retailer. The model I'm working with is called "Guitar" and all the guitar data is stored in ...

Adjusting table to include hashed passwords for migration

How can I convert a string password into a hash during migration? I've tried using the following code, but it seems the transaction completes after the selection: const users = await queryRunner.query('SELECT * FROM app_user;'); user ...

Discovering the object and its parent within a series of nested arrays

Is there a way to locate an object and its parent object within a nested array of unknown size using either lodash or native JavaScript? The structure of the array could resemble something like this: name: 'Submodule122'</p> I have been ...

Tips for displaying the date of a JSON response in Angular HTML format?

When working with Angular HTML, I am looping through a JSON array using ngFor and accessing the birth date data like this: <td>{{item.customer_info.birth_date}}</td> The data is being displayed as ddMMyyyy format, but I would like to change it ...

What is the reason for sending a single file to the server?

A function called "import File" was developed to send multiple files to the server, but only one file is being received. Input: <input type="files" id="files" name="files" multiple onChange={ (e) => this.importFile(e.target.files) } ...

Conceal the sidebar once user is logged in

I need a dynamic webpage; upon loading the login page, the sidebar should be hidden and the login page should occupy the full width of the page. Once the user successfully logs in, the sidebar along with all components should be displayed. I've attemp ...

Automatically Submitting React Forms Using Code

Hey there! I'm having some trouble getting my form to submit inside the handleSubmit function in React. I need to prefetch some information before submitting the form, but I can't seem to trigger the submission once I'm done with my operatio ...

When I bring in a component from my personal library, it will assign the type "any" to it

I'm facing an issue when trying to import a component from my own library. The component within the library is actually sourced from another one (so I import the component, customize it, and then export the customized version). However, upon importi ...

Is there a way for me to showcase a particular PDF file from an S3 bucket using a custom URL that corresponds to the object's name

Currently, I have a collection of PDFs stored on S3 and am in the process of developing an app that requires me to display these PDFs based on their object names. For instance, there is a PDF named "photosynthesis 1.pdf" located in the biology/ folder, and ...