Creating a message factory in Typescript using generics

One scenario in my application requires me to define message structures using a simple TypeScript generic along with a basic message factory. Here is the solution I devised:

export type Message<
  T extends string,
  P extends Record<string, any> = Record<string, never>
> = {
  type: T;
  payload: P;
};

export const msg = <M extends Message<string, Record<string, any>>>(
  type: M['type'],
  payload: M['payload']
): M => ({ type, payload });

type UserRegistered = Message<'UserRegistered', { name: string }>;

const message = <UserRegistered>msg('UserRegistered', { name: 'Test' });

However, I encounter a TS2322 error on the ): M => ({ type, payload }); line:

error TS2322: Type '{ type: M["type"]; payload: M["payload"]; }' is not assignable to type 'M'. '{ type: M["type"]; payload: M["payload"]; }' can be assigned to the 'M' constraint, but 'M' could potentially be instantiated with a different subtype of constraint 'Message<string, Record<string, any>>'.

I am grappling with understanding why this poses a risk to type safety and why it fails to transpile, given that it essentially involves destructuring and reconstructing the same type.

(In case you are questioning the necessity of this thin factory layer, I believe it will enhance maintainability by providing a large set of predefined named factory functions)

Answer â„–1

When inferring function arguments, it's best to start from the bottom and work your way up instead of trying to infer the entire generic type all at once. Begin by inferring each nested property within the type before combining them together.

In many cases, you don't need to specify an explicit return type, especially when working with TypeScript's structural type system rather than a nominal one.

Avoid explicitly specifying generics when calling a function as TypeScript is usually able to infer all types automatically.

export type Message<
    T extends string,
    P extends Record<string, any> = Record<string, never>
    > = {
        type: T;
        payload: P;
    };

export const msg = <
    Type extends string,
    Payload extends Record<string, any> = Record<string, never>
>(
    type: Type,
    payload: Payload
): Message<Type, Payload> => ({ type, payload })

type UserRegistered = Message<'UserRegistered', { name: string }>;

const message = msg('UserRegistered', { name: 'Test' })

/**
 * UserRegistered is redundant here
 */
const message_: UserRegistered = msg('UserRegistered', { name: 'Test' })

Playground

For more details on inferring function arguments, check out my article here.

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 persist with debugger functionality in browser development tools following an upgrade from Angular 8 to version 15

After upgrading from Angular version 8 to version 15, I've encountered an issue where the debugger is not functioning in any browser's developer tools. Can anyone provide some insight on what could be causing this problem? Is it related to the so ...

Obtain the selected type from a tuple after filtering

I have a tuple with multiple objects stored in it. const repos = [ { name: 'react', type: 'JS' }, { name: 'angular', type: 'TS' }, ] as const const RepoTypes = typeof repos const jsRepoTypes = FilterRepos<&a ...

Pausing in a NodeJS HTTP request listener until receiving another response before proceeding

Essentially, this is a web proxy. Within a request listener, I am creating another http request, reading its response, and passing it to the main response. But I have the challenge of needing to wait for the secondary request to complete before continuing. ...

Learn how to dynamically enable or disable the add and remove buttons in the PrimeNG PickList using Angular 2

I'm currently learning Angular 2 and I'm working on creating a dual list box using PrimeNG's pickList component (https://www.primefaces.org/primeng/#/picklist). Within the pickList, I have table data with 3 columns, along with ADD and REMO ...

What is the best way to categorize variables?

How can I organize variables together: export let findbyc$: Observable<Object>; export let findbyi$: Observable<Object>; export let findbyo$: Observable<Object>; export let findbyob$: Observable<Object>; I would like to group them ...

Using parameters and data type in Typescript

When I remove <IFirst extends {}, ISecond extends {}> from the declaration of this function, the compiler generates an error. Isn't the return value supposed to be the type after the double dot? What does <IFirst extends {}, ISecond extends { ...

Transforming FormData string key names into a Json Object that is easily accessible

Recently, I encountered an issue where my frontend (JS) was sending a request to my backend (Node Typescript) using FormData, which included images. Here is an example of how the data was being sent: https://i.stack.imgur.com/5uARo.png Upon logging the r ...

Ways to effectively utilize an interface incorporating props.children and other property varieties

Currently working on a project with Next.js and Typescript. In order to create a layout component, I defined the following interface: export interface AuxProps { children: React.ReactNode; pageTitle: 'string'; } The layout component code sn ...

Create dynamic breadcrumb trails using router paths

I am currently working on developing a streamlined breadcrumbs path for my application. My goal is to achieve this with the least amount of code possible. To accomplish this, I have implemented a method of listening to router events and extracting data fr ...

What is the best way to take any constructor type and transform it into a function type that can take the same arguments?

In the code snippet below, a class is created with a constructor that takes an argument of a generic type. This argument determines the type of the parameter received by the second argument. In this case, the first parameter sets the callback function&apos ...

Creating a popup trigger in ReactJS to activate when the browser tab is closed

I am currently working on an enrollment form that requires customer information. If a user fills out half of the form and then attempts to close the tab, I want to trigger a popup giving them the option to save and exit or simply exit. While I have a jQue ...

​Troubleshooting findOneAndUpdate in Next.js without using instances of the class - still no success

After successfully connecting to my MongoDB database and logging the confirmation, I attempted to use the updateUser function that incorporates findOneAndUpdate from Mongoose. Unfortunately, I ran into the following errors: Error: _models_user_model__WEBPA ...

How can a TypeScript function be used to retrieve a string (or JSON object)?

When attempting to retrieve data from a web API using TypeScript and return the JSON object, encountering an error has left me puzzled. Inside my function, I can successfully display the fetched data on the console, but when I try to return it with return ...

Creating a typescript type for contextual dispatch by leveraging the values of another interface

I am seeking to define a specific type for my "reducer" function. The "reducer" function I have takes in 2 parameters: the current state and the data sent in the dispatch context (to be used by the reducer). const reducer = ( state: any, props: { ...

"Exploring Angular: A guide to scrolling to the bottom of a page with

I am trying to implement a scroll function that goes all the way to the bottom of a specific section within a div. I have attempted using scrollIntoView, but it only scrolls halfway down the page instead of to the designated section. .ts file @ViewChild(" ...

Using ngIf for various types of keys within a JavaScript array

concerts: [ { key: "field_concerts_time", lbl: "Date" }, { key: ["field_concert_fromtime", "field_concert_totime"], lbl: "Time", concat: "to" }, { key: "field_concerts_agereq", lbl: "Age R ...

Angular 14 Observables are not triggering resize events

There seems to be an issue here, as the code is not being triggered at all. The console log is not printing and this.width is not changing. constructor(private host: ElementRef, private zone: NgZone) {} public ngOnInit(): void { this.observer = new Re ...

Accessing information independent of Observable data in TypeScript

When attempting to send an HttpRequest in Typescript, I encountered an issue where the received data could not be stored outside of the subscribe function. Despite successfully saving the data within the subscribe block and being able to access it there, ...

The offsetWidth of the nativeElement in Angular 2's ElementRef is consistently returning a value

Is there a way to retrieve the width of an element with a dynamic width using Angular 2? I can easily accomplish this with pure javascript, but not through Angular 2. constructor(private element: ElementRef) { // .... Obtaining the width let width = thi ...

Receiving error in TypeScript while using the 'required' attribute in the input field: "Cannot assign type 'string | undefined' to parameter expecting type 'string'"

In my TypeScript code, I am currently in the process of transitioning from utilizing useState to useRef for capturing text input values. This change is recommended when no additional manipulation necessitating state or rerenders is required. While I have ...