Incorporating tuple types into Typescript for type inference

After defining the types below

type Tool = 'network_query' | 'cluster'
type Network = 'one' | 'two' | 'three'

class QueryOneParams {...}
class QueryTwoParams {...}
class QueryThreeParams {...}
class ClusterParams {...}

The goal is to establish a connection between different combinations of Tool and Network, enabling a structure like this:

const queryParamsLookup = {
    'one': QueryOneParams,
    'two': QueryTwoParams,
    'three': QueryThreeParams
}

type Job<Tool, Network> = {
    id: string,
    params: JobParams<Tool, Network>
}

This would mean that:

  • JobParams<'network_query', 'one'>
    corresponds to QueryOneParams
  • JobParams<'network_query', 'two'>
    corresponds to QueryTwoParams
  • JobParams<'network_query', 'three'>
    corresponds to QueryThreeParams
  • JobParams<'cluster'> corresponds to ClusterParams
  • JobParams<'cluster', 'one'>
    ,
    JobParams<'cluster', 'two'>
    , and
    JobParams<'cluster', 'three'>
    are not valid

To achieve this, there's a need to define that the second generic parameter 'one' | 'two' | 'three' is only used if the first parameter is 'network_query'. As far as I know, TypeScript does not support optional generic parameters based on another parameter's type.

Is this accurate? Hopefully, it's possible!

Alternatively, a helper type has been created:

type NetworkQueryJobType = {
    [N in keyof typeof queryParamsLookup]: ['network_query', N]
}[keyof typeof queryParamsLookup]
// ['network_query', 'one'] | ['network_query', 'two'] | ['network_query', 'three']

type JobType = NetworkQueryJobType | ['cluster']
// ['network_query', 'one'] | ['network_query', 'two'] | ['network_query', 'three'] | ['cluster']

The definition of Job has been updated to:

type Job<JobType> = {
    id: string,
    params: JobParams<JobType>
}

Despite these changes, there are challenges with type inference within the mapper type JobParams:

type JobParams<T extends JobType> = T extends ['network_query', infer N] ?
typeof queryParamsLookup[N] // Error: N is not compatible with 'one' | 'two' | 'three'
: ClusterParams

To address the issue with type inference, the following workaround was implemented:

type JobParams<T extends JobType> = T extends ['network_query', infer N] ?
N extends Network ?
    typeof queryParamsLookup[N] // No error
    : ClusterParams
: ClusterParams

However, autocomplete performance remains subpar when typing certain expressions:

const params: JobParams<['

In situations like this, VSCode fails to offer suggestions for 'cluster' | 'network_query'

Overall, it seems like an uphill battle. Is there a fundamental mistake in this approach?

Playground Link

Answer №1

If you leverage default generic parameters along with a neutral type, like null, you can align with the expected behavior.

I am assuming certain aspects of your code here; please let me know if my interpretations are inaccurate.

Initially, the type of queryParamsLookup is as follows:

type QueryParamsLookup = {
    'one': QueryOneParams,
    'two': QueryTwoParams,
    'three': QueryThreeParams
}

If we define the object as presented in the question, it will assign types based on the provided class constructors, which may not be the intended outcome.

The JobParams type can be defined as shown below:

type JobParams<T extends Tool, N extends T extends "network_query" ? Network : null = T extends "network_query" ? Network : null> =
    N extends Network ? QueryParamsLookup[N] : ClusterParams

This definition meets the specified criteria:

// valid cases
const one: JobParams<"network_query", "one"> = new QueryOneParams()
const two: JobParams<"network_query", "two"> = new QueryTwoParams()
const three: JobParams<"network_query", "three"> = new QueryThreeParams()
const cluster: JobParams<"cluster"> = new ClusterParams()

// invalid cases
const inv1: JobParams<"cluster", "one"> = new QueryOneParams()
const inv2: JobParams<"cluster", "two"> = new QueryTwoParams()
const inv3: JobParams<"cluster", "three"> = new QueryThreeParams()

The current definition of JobParams takes a pessimistic approach when a union is supplied as its first argument, treating it as if it's not network_query. This behavior can be reversed by changing the ternary operators.

You can access a playground link showcasing the solution.

Though achievable with tuple generics, this approach may seem excessive and less intuitive in my view.

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

Utilizing References in React Components

One of the challenges I am facing involves a Container that needs references to some of its child components: const Container = () => { const blocks: HTMLDivElement[] = []; return ( <div> <Navigation currentBlock={currentBlock} ...

I am experiencing an issue with mydaterangepicker and primeng where it is not displaying properly in the table header. Can anyone assist me with this

I am attempting to integrate mydaterangepicker () with primeng turbotable (since primeng calendar does not meet the requirements), but I am having trouble with its display. Could you please assist me with some CSS code or suggest an alternative solution? ...

Swap references between two components at the same level

There are two instances of custom-component spawned by a shared parent with distinct data, each displayed as a tab in the mat-tab-group. <mat-tab-group> <mat-tab label="TAB1"> <ng-template matTabContent> <custom-componen ...

Angular HttpClient request fails to initiate

Overview: A button click on a form triggers the methodForm within the component. methodForm then calls methodService in the service layer. methodService is supposed to make an HTTP POST request. Problem: The HTTP POST request is not being made. However, me ...

Recursively map elements of a TypeScript array to keys of an object

I am looking to create a structured way to specify paths for accessing objects, ensuring that the path is correctly typed based on the object type. Let me illustrate with an example. Consider the following data: const obj = { name: 'Test', ...

Can you explain why it prints to the console twice every time I try to add an item?

This is a note-taking application created using Angular and Firebase. The primary functionalities include adding items and displaying them. I noticed a strange behavior where my ngOnInit() method is being called twice every time I add an item. As shown in ...

The InMemoryCache feature of Apollo quietly discards data associated with fragments that are declared on the main

After sending the following query to my GraphQL server: fragment B on root_query { foo { id } } query A { ...B } The data received from the server includes the foo field. However, when I retrieve it using Apollo's InMemoryCache a ...

Using Angular's ngForm within an ng-template

Within my ng-template, there is a form displayed in a modal. .ts @ViewChild('newControlForm', {static: false}) public newControlForm: NgForm; .html <ng-template> <form role="form" #newControlForm="ngForm"> </form> </ng ...

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 ...

What is the process of instantiating a class based on its name or type?

My classes are structured as follows: class FilterWeekScheduleClass { } class FilterClassJournal { } const registryFilterClasses = { FilterWeekScheduleClass, FilterClassJournal }; class SingletonClassRegister { public registeredClasses = {}; ...

Displaying properties of a class in Typescript using a default getter: Simplified guide

Here is an interface and a class that I am working with: export interface ISample { propA: string; propB: string; } export class Sample { private props = {} as ISample; public get propA(): string { return this.props.propA; } public se ...

Where can one find Typescript definitions when using typings?

Whenever I run typings install mongoose, it seems like the Typescript definitions are being fetched from https://github.com/louy/typed-mongoose This change feels a bit unexpected. Previously, they were sourced from DefinitelyTyped at https://github.com/ ...

invoke a method from a different class within the same component file

I am facing a situation where I have 2 classes within the same component.ts file. One class is responsible for embedding the Doc blot, while the other class serves as the main component class. I need to call a function that resides in the component class f ...

A guide on activating the <b-overlay> component when a child triggers an Axios request in vue.js

Is there a way to automatically activate the Bootstrap-vue overlay when any child element makes a request, such as using axios? I am looking for a solution that will trigger the overlay without manual intervention. <b-overlay> <child> ...

Error encountered while compiling an Asp.Net Core project due to exceeding the maximum allowable path length in the

Encountering a critical error during the build process with Visual Studio 2016 update 3 Asp.Net Core. The build is interrupted with the following message: Severity Code Description Project File Line Suppression State Error MSB4018 The "FindC ...

An issue has occurred while attempting to differentiate '[object Object]'. Please note that only arrays and iterable objects are permitted

myComponent.component.ts ngOnInit() { this.getData.getAllData().subscribe( response => { console.log(response); this.dataArray = response; }, () => console.log('there was an error') ); } myservi ...

Creating a see-through effect in Three.js with React Fiber

I'm currently working with react fiber and struggling to make the background of my child scene transparent. Below is my root component containing the main Canvas element: export const Splash = () => { const { intensity, distance, colo ...

Encountered "Function undefined error when invoking within a map in TypeScript"

In my function, there is a map that looks like this: mainFunc(){ // other logics data.map(function (item) { item.number = Math.round(item.number); item.total = item.last - item.first; item.quantity= item?.quantity ? quantityRange(ite ...

Using Typescript to combine strings with the newline character

Currently, I am delving into Angular2 and facing the challenge of creating a new line for my dynamically generated string. For example: input: Hello how are you ? output: Hello how are you? Below is the code snippet: .html <div class="row"> ...

Enhancing React TypeScript: Accurate typings for Route's location and children attributes

I am facing an issue with my router as it passes props of location and children, but I am uncertain about the correct types for these props. Here is the code snippet for the router using react-router-dom... import React, { useReducer } from 'react&a ...