The correlation of types between function parameters using function overloads in TypeScript

In the process of developing a factory function, I am incorporating a type argument to specify the type of object being created, along with a parameter argument containing parameters that describe the object. The parameters are dependent on the specific type of object being created. An example of this is shown below:

enum EntityType {
  TABLE,
  BUCKET
}
type TableParams = { tableName: string }
type BucketParams = { bucketName: string }

function createEntity (type: EntityType.TABLE, params: TableParams): void
function createEntity (type: EntityType.BUCKET, params: BucketParams): void
function createEntity (type: EntityType, params: TableParams | BucketParams): void {
  switch(type) {
    case EntityType.TABLE:
      console.log('table name:', params.tableName)
      break
    case EntityType.BUCKET:
      console.log('bucket name:', params.bucketName)
      break
  }
}

The use of function overloads ensures that users can only invoke the function with the appropriate parameters based on the entity type, for example:

createEntity(EntityType.TABLE, { tableName: 'foo' })
createEntity(EntityType.BUCKET, { bucketName: 'bar' })
createEntity(EntityType.BUCKET, { tableName: 'fox' }) // => No overload matches this call.

The first two calls execute successfully, however, the third call results in a compilation error stating "No overload matches this call."

Yet how can I enforce type safety within the function? In other words, the provided sample does not compile because of the message "Property 'tableName' does not exist on type 'TableParams | BucketParams'."

Isn't TypeScript supposed to infer the type of the params argument based on which of the two function overloads is matched?

Answer №1

To eliminate the need for an enum and avoid using a typeguard, you can combine all parameters within one interface type.

interface EntityData {
    type: { new( ...args : any ) : BaseEntity };
    // Add shared member params
}

interface TableData extends EntityData {
    type: typeof TableEntity;
    tableName: string;
}

interface BucketData extends EntityData {
    type: typeof BucketEntity;
    bucketName: string;
}

class BaseEntity {
    constructor( data : EntityData ) {
        //assign any shared member params here
    }
}

class TableEntity extends BaseEntity {
    public tableName : string;

    constructor( data : TableData ) {
        super(data);
        this.tableName = data.tableName;
        console.log('table name:',this.tableName);
    }
}

class BucketEntity extends BaseEntity {
    public bucketName : string;

    constructor( data : BucketData ) {
        super(data);
        this.bucketName = data.bucketName;
        console.log('bucket name:', params.bucketName);
    }
}

function createEntity( data: TableData ): BaseEntity | undefined;
function createEntity( data: BucketData ): BaseEntity | undefined;
function createEntity( data: EntityData ): BaseEntity | undefined {
    if ( data ) {
        return new data.type(data );
    }
    return undefined;
}

createEntity( { type : TableEntity, tableName : 'foo'});

A more type-safe approach is to use it as follows:

let myTableData : TableData = { 
    type: TableEntity,
    tableName : 'foo'
}

createEntity( myTableData );

// The above will compile
// The below 2 examples will result in compile errors

let myTableData2 : TableData = { 
    type: BucketEntity,
    tableName : 'foo'
} // Will give type error for BucketEntity here

let myTableData3 : TableData = {
    type: TableEntity,
    bucketName : 'foo'
}  // Will give error here for bucketName not existing in TableData

If you still want to use overloads, consider implementing a typeguard inside the switch statement for proper typing

enum EntityType {
  TABLE,
  BUCKET
}
type TableParams = { tableName: string }
type BucketParams = { bucketName: string }

function createEntity (type: EntityType.TABLE, params: TableParams): void
function createEntity (type: EntityType.BUCKET, params: BucketParams): void
function createEntity (type: EntityType, params: TableParams | BucketParams): void {
  switch(type) {
    case EntityType.TABLE:
        if( paramsAreTableParams( params ) ) {
            console.log('table name:', params.tableName);
        } else {
            console.log('incorrect params');
        }
        break;
    case EntityType.BUCKET:
        if( paramsAreBucketParams( params ) ) {
            console.log('table name:', params.bucketName);
        } else {
            console.log('incorrect params');
        }
        break;
  }
}

function paramsAreTableParams( params : TableParams | BucketParams ) : params is TableParams {
    return ( params as TableParams).tableName != undefined;
}

function paramsAreBucketParams( params : TableParams | BucketParams ) : params is BucketParams {
    return ( params as BucketParams).bucketName != undefined;
}

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

Tips for effectively overriding a method in typescript

Why is this.fullName appearing empty in the show() method? class Person { protected name: string = ""; constructor(name: string) { this.makeSir(name); } makeSir(name: string) { this.name = "sir" + name; } } class M ...

Utilizing epics in conjunction with typesafe-actions and Connected React Router for seamless integration and enhanced

Presently, I am in the process of creating a scenario where an epic is established to monitor actions of type LOCATION_CHANGE or LocationChangeAction. This action is triggered whenever changes occur in the router history as a result of router actions like ...

Potential absence of object.ts(2531)

Currently, I am working on a project using Node.js with Typescript. My task involves finding a specific MongoDB document, updating certain values within it, and then saving the changes made. However, when I try to save the updated document, an error is bei ...

When using nodejs with sqlite3, the first callback parameter returns the class instance. How can this be resolved in order to prevent any issues?

Exploring a TypeScript class: class Log { public id: number; public text: string; construct(text: string){ this.text = text; } save(){ db.run( `insert into logs(text) values (?) `, this.text, ...

Illustrative demonstration of Vue with TypeScript

I am currently working on developing a HelloWorld application using Vue.js and TypeScript. index.html <script data-main="app.js" src="node_modules/requirejs/require.js"></script> <div id="app">{{text}}</div> app.ts import Vue f ...

What is the reason behind the restriction on using 'this' on the left side of an assignment?

Within the component class, I've been working on this: export class myapp { detail; myarr = ['me', 'myself', 'i']; title = this.myarr[0]; this.detail = this.title ; //error } I'm curious why `this.detail` ...

What is the process for importing a TypeScript module from the local directory?

I am currently working on a TypeScript module with plans to eventually release it on NPM. However, before publishing, I want to import the module into another project hosted locally for testing purposes. Both projects are written in TypeScript. The TypeSc ...

Guide on importing an ES6 package into an Express Typescript Project that is being utilized by a Vite React package

My goal is to efficiently share zod models and JS functions between the backend (Express & TS) and frontend (Vite React) using a shared library stored on a gcloud npm repository. Although the shared library works flawlessly on the frontend, I continue to e ...

Exploring Angular 2's nested navigation using the latest router technology

Is there a way to implement nested navigation in Angular? I had this functionality with the previous router setup. { path: '/admin/...', component: AdminLayoutComponent } It seems that since rc1 of angular2, this feature is no longer supported. ...

Navigating through ionic2 with angularjs2 using for-each loops

I developed an application using IONIC-2 Beta version and I am interested in incorporating a for-each loop. Can anyone advise if it is possible to use for each in Angular-V2? Thank you. ...

What is the best way to implement a generic parameter with constraints in an abstract method?

Take a look at this scenario: interface BaseArgs { service: string } abstract class BaseClass { constructor(name: string, args: BaseArgs) { this.setFields(args) } abstract setFields<T extends BaseArgs>(args: T): void } interface ChildA ...

Compare the values of properties in an array with those in a separate array to filter in Angular/TypeScript

There are two arrays at my disposal. //1st array tasks.push({ ID: 1, Address: "---", Latitude: 312313, Longitude: 21312 }); tasks.push({ ID: 3, Address: "---", Latitude: 312313, Longitude: 21312 }); //2nd array agentTasks.push({ID:2,AgentID: 2,TaskID:1}); ...

Tips for querying enum data type using GraphQL

I am having trouble querying an enum from GraphQL in my Nest.js with GraphQL project. I keep getting an error message saying: "Enum 'TraitReportType' cannot represent value: 'EMBEDDED'". I have tried using type:EMBEEDED, but it did not ...

Typescript - Specifying the return type for a deeply nested object

I have a container that holds multiple sub-containers, each containing key-value pairs where the value is a URL. I also have a function that takes the name of one of the sub-containers as input, loops through its entries, fetches data from the URLs, and re ...

Here is a guide on implementing Hash in URLs with React Router

I'm brand new to React and running into an issue. My page has two tabs and I would like to create a hash URL that will redirect to the corresponding tab based on the URL hash. Additionally, when I change tabs, I want the URL to update as well. Please ...

Guide to automatically installing @types for all node modules

As a newcomer to Typescript and NodeJs, I have been experiencing errors when mentioning node modules in my package.json file and trying to import them. The error messages I always encounter are as follows: Could not find a declaration file for module &apos ...

Understanding how to deduce parameter types in TypeScript

How can I infer the parameter type? I am working on creating a state management library that is similar to Redux, but I am having trouble defining types for it. Here is the prototype: interface IModel<S, A> { state: S action: IActions<S, A&g ...

Type definitions in Typescript for the style property of Animated.View

One of my components has a Props interface that extends ViewProps from React Native, like this: export interface Props extends ViewProps { // Custom props } As a result, this also extends the style prop. However, I am facing an issue while using Animat ...

Color key in square shape for graph legend

I am looking for legend colors in square shape, but I don't want them to appear as square boxes on the graph. https://i.stack.imgur.com/Of0AM.png The squares are also showing up on the graph, which is not what I want. https://i.stack.imgur.com/Az9G ...

How come the splice method is changing the value of the original object?

There's something strange happening with this code I'm trying out. Code: const x = [{ a: 'alpha', b: 'beta' }, { a: 'gamma' }]; const y = x[0]; y.a = 'delta'; x.splice(1, 0, y) console.log(x) Output: [ ...