Develop an item with a function that takes an input of the identical type as a variable within the item itself

I am facing a challenge with managing an array of objects that represent commands for my game. Each object includes an input field to define the types of arguments expected by the command. The purpose of this setup is to enable validation on the arguments before executing the command using a resolve function. However, I encounter an issue where I lose type safety within the resolve() function, as all the arguments are treated as strings despite being validated differently earlier. Here is an example of one such object:

const commands = [{
name: "setscore",
input: [["b", "blue", "r", "red"] as const, Number()],
resolve({input}) {

// Validation results are lost and arguments are treated as strings
const [teamName, score] = input

// Additional casting required when using these arguments in functions
}
}]

To address this issue, I aim to change the input type from string[] to

["b" | "blue" | "r" | "red", number]

This adjustment will be specific to each command, as they may have different argument requirements. My inspiration for this approach comes from trpc's methodology for achieving type safety in mutations, although I am still exploring how to implement it effectively.

How can I perform this desired casting? Initially, I considered creating a type with a Generic but encountered limitations when dealing with arrays later in the process.

type Command<T> {
name: string,
input: T,
resolve({input}: {input: T}) => void
}

Answer №1

To simplify this situation, consider making the Command object generic, as demonstrated in your example:

type Command<T> = {
    name: string,
    input: T,
    resolve({ input }: { input: T }): void
}

Although you will need to specify the generic type later on, it is advantageous if you want flexibility in calling the resolve() method with various inputs beyond just the default input property.

However, manual specification of the type argument is not mandatory. You can utilize generic helper functions to automatically infer it. For instance, using the following function:

const asCommand = <T,>(c: Command<T>) => c;

You can deduce the type argument for a specific command:

const command = asCommand({
    name: "setscore",
    input: [["b", "blue", "r", "red"], Number()] as const,
    resolve({ input }) {
        const [teamName, score] = input
        // const teamName: readonly ["b", "blue", "r", "red"]
        // const score: number
    }
});
//const command: Command<readonly [readonly ["b", "blue", "r", "red"], number]>

In this code snippet, the compiler recognizes T as

readonly [readonly ["b", "blue", "r", "red"], number]
thanks to the const assertion placed on input.

Additionally, by employing another helper function:

const asCommandArray = <T extends any[]>(
    ...arr: { [I in keyof T]: Command<T[I]> }) => arr;

You can configure an array of commands with distinct type arguments using inference from mapped array types:

const arr = asCommandArray(
    { name: "x", input: "abc", resolve({ input }) { console.log(input.toUpperCase()) } },
    { name: "y", input: 123, resolve({ input }) { console.log(input.toFixed()) } }
)
// const arr: [Command<string>, Command<number>]

This implementation should align with your requirements.


The strategies mentioned above assume that the type of input is significant, necessitating tracking the type argument T for each command object, especially if you plan to invoke command.resolve() with diverse inputs. If, however, you only intend to use command.input consistently, you could redesign without requiring a generic type argument. In essence, this would be an existentially quantified generic denoted as Command<exists T>, where exists T signifies the existence of a T without concerning its specifics. Although TypeScript does not directly support existential types, you can simulate them through some form of inversion of control:

type SomeCommand = <R, >(cb: <T, >(command: Command<T>) => R) => R;
const someCommand = <T,>(command: Command<T>): SomeCommand => cb => cb(command);

const sc = someCommand({
    name: "setscore",
    input: [["b", "blue", "r", "red"], Number()] as const,
    resolve({ input }) {
        const [teamName, score] = input
        console.log("teamName", teamName, "score", score.toFixed(1));
    }
});

Now, sc has a type of SomeCommand, and you can only access its properties by passing a generic callback:

sc(command => command.resolve({ input: command.input }));
// "teamName",  ["b", "blue", "r", "red"],  "score",  "0.0" 

Nonetheless, at this point, when the real utility lies in invoking resolve() with just one acceptable input, simplifying the structure might be more practical:

type SomeCommand = { name: string, callResolve(): void }
const someCommand = <T,>({ name, resolve, input }: Command<T>): SomeCommand =>
    ({ name, callResolve: () => resolve({ input }) });

const sc = someCommand({
    name: "setscore",
    input: [["b", "blue", "r", "red"], Number()] as const,
    resolve({ input }) {
        const [teamName, score] = input
        console.log("teamName", teamName, "score", score.toFixed(1));
    }
});
sc.callResolve();
// "teamName",  ["b", "blue", "r", "red"],  "score",  "0.0" 

This modification exposes a zero-argument callResolve() method instead of the resolve/input pair, streamlining operations to execute the intended task efficiently.


Hence, you have two choices: either maintain Command as generic along with monitoring the type argument effort or customize Command specifically while refining it to offer only essential non-generic functionalities required.

Playground link to code

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

Positioning a Box tag at the bottom using MUI 5

My goal is to position a Box tag at the bottom of the page. Current Behavior: https://i.stack.imgur.com/ZupNo.png I am looking to place a TextField and send button at the bottom of the page on both browser and mobile. I want user messages to be above th ...

Does moment/moment-timezone have a feature that allows for the conversion of a timezone name into a more easily comprehendible format?

Consider this example project where a timezone name needs to be converted to a more readable format. For instance: input: America/Los_Angeles output: America Los Angeles While "America/Los_Angeles" may seem human-readable, the requirement is to convert ...

Splitting a string in Typescript based on regex group that identifies digits from the end

Looking to separate a string in a specific format - text = "a bunch of words 22 minutes ago some additional text". Only interested in the portion before the digits, like "a bunch of words". The string may contain 'minute', & ...

A method for consolidating multiple enum declarations in a single TypeScript file and exporting them under a single statement to avoid direct exposure of individual enums

I am looking to consolidate multiple enums in a single file and export them under one export statement. Then, when I import this unified file in another file, I should be able to access any specific enum as needed. My current setup involves having 2 separ ...

Having conflicting useEffects?

I often encounter this problem. When I chain useEffects to trigger after state changes, some of the useEffects in the chain have overlapping dependencies that cause them both to be triggered simultaneously instead of sequentially following a state change. ...

Create a Referral Program page within a swapping interface similar to the functionalities found in platforms like PancakeSwap, PantherSwap, and

Currently, my goal is to create a referral program page similar to the one found at . I have explored the source code on GitHub for the PantherSwap interface, but unfortunately, I did not come across any references to that specific section. Would you be ...

A guide on converting TypeScript to JavaScript while utilizing top-level await

Exploring the capabilities of top-level await introduced with TypeScript 3.8 in a NodeJS setting. Here's an example of TypeScript code utilizing this feature: import { getDoctorsPage } from "./utils/axios.provider"; const page = await getDo ...

Testing in Jasmine: Verifying if ngModelChange triggers the function or not

While running unit tests for my Angular app, I encountered an issue with spying on a function that is called upon ngModelChange. I am testing the logic inside this function but my test fails to spy on whether it has been called or not! component.spec.js ...

What distinguishes Angular directives as classes rather than functions?

When using Ng directives within HTML tags (view), they appear to resemble functions that are called upon rather than instances of a class. It almost feels like they could be static methods that can be invoked without an instance of a class. Comin ...

Discovering a specific string within an array of nested objects

Seeking guidance on creating a dynamic menu of teams using JavaScript/TypeScript and unsure about the approach to take. Here is an example dataset: const data = [ { 'name': 'Alex A', 'agentId': '1225& ...

Encountered an issue when attempting to include the "for" attribute to a label within an angular 2.0 template

I am currently using Angular 2.0 framework and working on developing an input component. In addition to that, I am also utilizing Google MDL which requires a specific HTML structure with labels for inputs. However, when trying to implement this, I encounte ...

Guide to customizing the layout preview specifically for certain components in Storybook, without affecting all components

Currently working on my storybook project and facing an issue. I'm aiming to have the layout centered in the preview section. I attempted export const parameters = { layout: 'centered', }; in the .storybook/preview.js file However, this c ...

Unable to find custom components when using react-router

My goal is to improve the organization of my Routes in React and separate concerns. I am currently utilizing react-router-dom version 5. Within my Application Routes component, I have structured it with 3 children components: AuthenticatedRoutes PublicRo ...

What does the "start" script do in the package.json file for Angular 2 when running "concurrent "npm run tsc:w" "npm run lite"" command?

What is the purpose of concurrent in this code snippet? "scripts": { "tsc": "tsc", "tsc:w": "tsc -w", "lite": "lite-server", "start": "Concurrent npm run tsc:w npm run lite" } ...

Tips for formatting dates in Angular 6

I am currently working on a function that displays real-time dates based on user input. Currently, when the user enters the input, it is displayed in the front end as follows: 28.10.2018 10:09 However, I would like the date to change dynamically based on ...

"Experience the seamless navigation features of React Navigation V6 in conjunction with

Hello there, I've been experimenting with react-navigation 6 in an attempt to show a modal with presentation: "modal" as instructed on the documentation. However, I'm facing an issue where the modal is not displaying correctly and appears like a ...

Sidenav selector unable to display Angular component

I'm facing a dilemma. I have the following code in my app.component.html file: <mat-sidenav-container class="sidenav-container"> <app-sidenav></app-sidenav> <mat-sidenav-content> <app-header></app-header> ...

Typescript Server Problem: Critical Error - Mark-compacts Inefficiently Close to Heap Limit, Allocation Unsuccessful - JavaScript Heap Exhausted

Whenever I run my CRA project, I consistently encounter this error in my console. It seems to be related to the typescript server. Is there a solution for this issue? 99% done plugins webpack-hot-middlewarewebpack built preview 7c330f0bfd3e44c3a97b in 64 ...

What is the appropriate event type to pass to the onKeyPressed function in a React application utilizing MaterialUI and written with Typescript?

I am currently working on a React application using Typescript and MaterialUI, where I have implemented a TextField component. My goal is to capture the value of the input HTML element when the user presses the enter key. To achieve this, I have created ...

What could be causing my TSC to constantly crash whenever I try to utilize MUI props?

Currently in the process of converting a JavaScript project using Next.js and Material UI to TypeScript. This is a snippet of code from one of my components. Whenever I include Props as an intersection type along with MUI's BoxProps, the TypeScript c ...