Tips for validating that a TypeScript parameter is a union with a specific type

Is there a way to create a TypeScript function that confirms an argument is a union type containing another, more specific union? Here's an example scenario:

type Command = { 
  name: string 
  [key: string]: any
}

type Insert = { name: 'insert'; text: string }
type Delete = { name: 'delete'; position: number }
type CoreCommand = Insert | Delete

type Replace = { name: 'replace'; position: number; text: string }
type CustomCommand = CoreCommand | Replace

const exec = <T ???>(command: T) => {
  switch (command.name) {
    case 'insert':
      // ...
      break
    case 'delete':
      // ...
      break
    // ...
  }
}

In the code above, how can we guarantee that the generic type T always contains the CoreCommand union type, while still allowing for other "custom" commands?

Additionally, how do we prevent conflicts between custom commands and core commands?

Answer №1

One key aspect to consider is that your CustomCommand already encompasses the CoreCommand within its expression as CoreCommand | Replace equates to Insert | Delete | Replace. It seems there is no immediate need for polymorphism in this scenario; you can simply utilize the CustomCommand:

const exec = (command: CustomCommand) => {
  switch (command.name) { // Command name is properly insert | delete | replace
    case 'insert':
      // ...
      break
    case 'delete':
      // ...
      break
    // ...
  }

Another consideration is how to ensure that any newly created commands adhere to the interface of the Command. This can be achieved through conditional types that verify if the name does not already exist in CoreCommands:

type CreateCommand<T extends Command> = T['name'] extends CoreCommand['name'] ? never : T; 
type Insert2 = CreateCommand<{ name: 'insert'; position: number; text: string }>

type CustomCommand = CoreCommand | Insert2 // Resulting in Insert | Delete only

The use of CreateCommand will result in "never" if a type with a similar name property already exists in core commands. Thus, types sharing the same name field will be omitted from the union.

You could also create an additional utility type for creating a union that always incorporates CoreCommands:

type Replace = CreateCommand<{ name: 'replace'; position: number; text: string }>
type Change = CreateCommand<{ name: 'change'; position: number; text: string }>

type MakeCommands<NewCommand extends Command> = CoreCommand | NewCommand
type CustomCommand = MakeCommands<Replace | Change>; // Ensuring CoreCommand is always included

Finally, let's discuss making the exec function polymorphic:

const exec = <C extends Command>(command: C, f: (x: C) => void) => f(command);

const comm = { name: 'replace', position: 1, text: 'text' };
// The 'as' statement here blocks direct inference by TS from const, it is unnecessary in real code
const result = exec(comm as CustomCommand, (incommand) => {
  switch (incommand.name) { // Incommand refers to CustomCommand here
    case 'insert':
      // ...
      break
    case 'delete':
      // ...
      break
    // ...
  }
});

In the above implementation, exec infers the type from the first argument and passes the inferred type to the function as the second argument. This approach ensures a specified type containing CoreCommands and any command included in the type provided as the command argument.

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

Webpack is having trouble identifying Node's process module

It has been more than ten years since I last worked with JavaScript, but recently I had an idea for an app that would be best implemented as a NodeJS app. As I delved into the modern JS ecosystem, like many others, I found myself thoroughly confused, haha. ...

How to Maintain Default Styling in Next.js with Material UI When Disabling Accordion Feature

I am currently working on a project using Next.js and incorporating Material UI for the user interface elements. One particular challenge I am facing is with an Accordion component that needs to be disabled under specific conditions, but still appear witho ...

I am interested in using a loop in Angular to highlight my div element

Enhancing my comprehension regarding the mentioned images. If I don't select anything within the div property, the default style (css) should appear like this, at least when one div is selected. However, the issue arises when unable to select. This ...

Ways to update the component's state externally

I'm new to Next.js (and React) and I'm attempting to update the state of a component from outside the component. Essentially, I am conditionally rendering HTML in the component and have a button inside the component that triggers a function to se ...

Experiencing unfamiliar typescript glitches while attempting to compile a turborepo initiative

I have been utilizing the turborepo-template for my project. Initially, everything was running smoothly until TypeScript suddenly started displaying peculiar errors. ../../packages/fork-me/src/client/star-me/star-me.tsx:19:4 nextjs-example:build: Type erro ...

BarChart is failing to exhibit data in tooltips when using dynamic keys

Query Description Hello! I'm currently tackling an issue with a bar chart. Everything is working smoothly, except for the default tooltip, which appears blank when hovering over the bars. My chart utilizes dynamic keys for the legends, and they are f ...

Ways to circumvent ng switch and create a component based on type

In my current code, I have an array called resourceTypes and I am using ngSwitch to create different components/directives based on the TypeName. However, I find this approach cumbersome as I have to update the code every time I add a new resource editor. ...

React Typescript Mui causing `onBlur` event to trigger with every change occurring

I'm currently developing a front-end application using Typescript and React with MUI. The form code I have written is as follows: <TextField label="Password" value={this.state.password} placeholder="Choose a password" type="password" onC ...

The specified main access point, "@angular/cdk/platform", is lacking in required dependencies

I recently updated my Angular app from version 8 to version 9. After resolving all compilation and linter errors, I encountered one specific issue that is causing me trouble: ERROR in The target entry-point "@angular/cdk/platform" has missing dep ...

Choose a single asset from the list of values stored in the Map

I'm looking to implement something similar to the following: let myMap = new Map<string, any>(); myMap.set("aaa", {a: 1, b: 2, c:3}); myMap.set("bbb", {a: 1, b: 2, c:6}); myMap.set("ccc", {a: 1, b: 2, c:9}); let cs = myMap.values().map(x => ...

Can someone explain why the Next 13 API route is showing up as empty?

I am currently working with Next 13 and I am attempting to create a simple API route. My goal is to have a: "hi" returned when I visit localhost:3000/api/auth. Despite not encountering a 404 error or any errors in the terminal or console, I can&a ...

Issues with sending emails through Nodemailer in a Next.js project using Typescript

I'm currently working on a personal project using Nodemailer along with Next.js and Typescript. This is my first time incorporating Nodemailer into my project, and I've encountered some issues while trying to make it work. I've been followin ...

`How can I extract HTMLElements from slots in vue3?`

When attempting to develop a Layer component, I encountered some challenges. Here is the code: // Wrapper.vue <template> <slot v-bind="attrs"></slot> </template> <script lang="ts" setup> import { defi ...

Steps to automatically make jest mocked functions throw an error:

When using jest-mock-extended to create a mock like this: export interface SomeClient { someFunction(): number; someOtherFunction(): number; } const mockClient = mock<SomeClient>(); mockClient.someFunction.mockImplementation(() => 1); The d ...

Is there a convenient HTML parser that is compatible with Nativescript?

I have tested various libraries like Jquery, Parse5, and JsDom, but unfortunately they are not compatible with nativescript. Jquery relies on the DOM, while Parse5 and JsDom require Node.js which is currently not supported by nativescript. I am in need of ...

After selecting an item, the Next UI navbar menu seems to have trouble closing

Having trouble with the navbar menu component not closing when an option is selected from the menu. The menu does open and close successfully within the menu icon. I attempted to use onPress() but it doesn't seem to be working as expected. "use c ...

Invoke a function in Angular when the value of a textarea is altered using JavaScript

Currently, I am working with angular and need to trigger my function codeInputChanged() each time the content of a textarea is modified either manually or programmatically using JavaScript. This is how my HTML for the textarea appears: <textarea class ...

What is the process of importing types in TypeScript?

Here is the export I am working with: import * as jwt from 'jsonwebtoken'; ... export type MyJsonWebToken = typeof jwt; Next, when attempting to use it in my code: export class AuthService { constructor( @Inject(constants.JWT) private ...

"This error message states that the use of an import statement outside a module is not allowed

After searching for a solution without any luck, I decided to start a new discussion on this topic. Currently, I am working on azure functions using Typescript and encountering the following error: import { Entity, BaseEntity, PrimaryColumn, Column, Many ...

Webpack does not support d3-tip in its current configuration

I'm having some trouble getting d3-tip to work with webpack while using TypeScript. Whenever I try to trigger mouseover events, I get an error saying "Uncaught TypeError: Cannot read property 'target' of null". This issue arises because th ...