4

I'm currently writing some middleware in typescript (transpiles to JavaScript runs on NodeJS). This middleware uses Typeorm in most cases. But sometimes you want to do crazy stuff with SQL. So i wrote some code that sends raw SQL queries and returns them like this:

public query(query: string): Promise<RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[]> {
    return new Promise<RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[]>((resolve, reject) => {
        this.status = DBCstatus.busy;
        this.mysqlConnection.query(query, (err, result, fields)=> {
            this.lastAnswerTime = Date.now();
            if (err === null) {
                this.status = DBCstatus.open;
                resolve(result)
            } else {
                reject(err);
            }
        })
    })
}

So far so good right?

Well not really. Every consumer of this function would have to check whats acutally inside this response.

What i want

I should be able to choose between two basic functions:

  1. Select Queries
  2. Update / Insert into etc.

Lets focus on the function which should be consumed like this:

let cars: cars[] = await queryForData<cars>("Select * from cars");

What i got so far

public queryForData<t>(query: string): Promise<t[]> {
    return new Promise<t[]>( async (resolve, reject) => {
        let queryResult: RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[];
        let dataFromQuery: t[] = [];
        try {
            queryResult = await this.query(query);

            if (queryResult instanceof Array) {
                if (queryResult.length > 0) {
                    if (queryResult[0].constructor.name === QueryResultType.textRow) {
                        let rowData: t[] = queryResult as t[];
                        return rowData;
                    } else {
                        reject(QueryError.unexpectedQueryResult)
                    }
                } else {
                    resolve(dataFromQuery);
                }
            } else {
                reject(QueryError.unexpectedQueryResult)
            }

            resolve([]);
        } catch(error) {
            reject(error);
        }
    })
}

Ok cool it works! But what's your Problem then

If i would consume the function like this:

let foo: Foo[] = await queryForData<Foo>("Select * from Bar");

It would work aswell because typescript would not check if the definition of Foo would match Bar. So there would be errors later while working with foo

What can be done ?

public queryForData<t>( example: t, query: string): Promise<t[]> {
 
   let queryResult: RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[];
   queryResult = await this.query(query);
   if (queryResult instanceof Array) {
      if (queryResult.length > 0) {
        for (let attr in example) {
           // basicly 
           if (queryResult[0][attr] === undefined) {
               // error
           }
        }
    }
}

Would this be a right way of doing things? Is this overkill, since i know the model of the Database? As always thanks in advance!

EDIT Jan 2021

In the last few months i found a way to validate unknown objects for my needs. I've used annotations in the object. I found it to be overkill for when i consume my own database, since i know the model beforehand but it was quiet usefull for consuming 3rd part interfaces or DB. If you want to see how i did it you can look it up here: https://github.com/SpaceNinjaApe/advanced-express-api/tree/master/src/property-validator

Cheers

SpaceNinjaApe
  • 312
  • 1
  • 13

0 Answers0