简体   繁体   中英

Function with dynamic return in typescript

I am trying to create an API on NodeJS with typescript

I have the following interfaces :

export interface ISqlResonse<T> {
success?:   string
error?:     string
data:       Array<T> //Nothing | array of object when are db operations
}

export interface IApiResponse<T> {
status:     'error' | 'success'
message:    string
data:       Array<T>
}

Each api call call a function that call an generic class name DB that select/insert/update/delate data from an database For example the update function look like :

 async updateData(input: IUpdateParam) : Promise<ISqlResonse<object>> {
    ...
    ...
}

API function call DB and look like :

async update(req): Promise<IApiResponse<IAccessPointsTableStructure>> {
    let data        = req.body ;
    let updateObj   = {
        data ,
        table: 'accessPoints',
        excludeColumns: 'loggedUserId',
        additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
        validationRules,
        where: `id=${data.id}`,
        returningData: true
    }

    let sqlResults = await db.updateData(updateObj) ; //  !!!

    if(typeof sqlResults.error==="string") {
        logger.log('error','Error on updating Access Points!',{sql: db.getQuery(), error: sqlResults.error});
        return({status:'error',message: 'Error on updating Access Points!',data: sqlResults.data});
    }

    logger.log('success', 'Access Points data updated with success!');
    return({status: 'error', message: 'Access Points data updated with success!', data: sqlResults.data})
}

My question is : how can I call the function db.updateData() and tell this function that I want to receive in data from ISqlResponse an array with objects like interface IAccessPointsTableStructure.

With other words i want to control the returning type of function. I teste several times with different approaches . (Replace wit in db.updateData(...) <..>... Thank you in advice.

You haven't included the definition of IUpdateParam , but I will assume that its table property is what decides the type of thing updateData() returns. Everywhere I've commented "guess" is just for example; you should change them to fit your use cases.


You should be able to modify the signature for the updateData() to reflect the relationship between the type of IUpdateParam passed in and the type of Promise<ISqlResponse<{}>> returned. Here's one way to do it, using generics (you could use overloads instead). First, declare a type to represent the mapping between the table names and the data type for each table. For example:

export type TableNameToTypeMapping = {
  accessPoints: IAccessPointsTableStructure,
  otherThings: IOtherThingsTableStructure, // guess
  // ...
}

Now, you can change the definition of IUpdateParam so that it only accepts the right values for table :

export interface IUpdateParam<K extends keyof TableNameToTypeMapping> {
        data: any, // guess
        table: K, 
        excludeColumns: string, // guess
        additionalColumns: any, // guess
        validationRules: any, // guess
        where: string // guess
}

So an IUpdateParam<'accessPoints'> object is meant to deal with the accessPoints table, and it is different from an IUpdateParam<'otherThings'> object.

Now the signature for updateData() can be changed to:

async updateData<K extends keyof TableNameToTypeMapping>(
  input: IUpdateParam<K>
): Promise<ISqlResonse<TableNameToTypeMapping[K]>> {
    // implement this!  The implementation is likely not
    // type-safe unless you use runtime type guards
  }

This means if you call updateData with a parameter of type IUpdateParam<'accessPoints'> , you will get back a Promise<ISqlResponse<TableNameToTypeMapping['accessPoints']>> . But TableNameToTypeMapping['accessPoints'] is just IAccessPointsTableStructure , so you are getting back a Promise<ISqlResponse<IAccessPointsTableStructure>> as desired.


Note that the object literal updateObj will have its table property inferred as type string , which is too wide. To make sure the call to updateData() works as desired, you will either need to assert that the updateObj.table property is of literal type 'accessPoints' , like this:

let updateObj = {
    data,
    table: 'accessPoints' as 'accessPoints', // assertion
    excludeColumns: 'loggedUserId',
    additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
    validationRules,
    where: `id=${data.id}`,
    returningData: true
}

or you will need to declare that updateObj is of type IUpdateParam<'accessPoints'> , like this:

// type declaration
let updateObj:IUpdateParam<'accessPoints'> = {
    data ,
    table: 'accessPoints',
    excludeColumns: 'loggedUserId',
    additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
    validationRules,
    where: `id=${data.id}`,
    returningData: true
}

Either way should work.


Hope that helps; good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM