I’m new to TypeScript, and trying to slowly migrate an existing JS (react) project to TypeScript.
I have a function executing a query:
/** * Run a Query against the currently connected database * @param {String} sql Query to run * @param {Object[]} Array of objects to be used in the prepared statement * @returns {Promise<Object[]>} Promise resolving after query is done. Returns an array of arrays with the result. */ const query = (sql, params) => { //Database stuff }
And I’m calling this in numerous locations:
const result = await query('SELECT count(*) as cnt from Table id=?',[2]') return result[0].cnt
The last line gives an error, because the field ‘cnt’ is unknown. How to best solve this? The return type is always Object[], but the content ob the object depends on the query.
Any suggestions how to best move this code to TS?
Advertisement
Answer
This is one of those places where the type system has to just be told what you’re expecting. Naturally, you have to take great care when doing that to ensure you’re giving the type system accurate information. Typically you want to do this as early as possible so that the more complex code built on it can rely on the types.
So an answer in two parts:
How to type
query
, andA suggestion for avoiding using
query
directly
First, you can make query
a generic function by giving it a generic type parameter to tell it what kinds of objects it returns:
/** * Run a Query against the currently connected database * @param {string} sql Query to run * @param {any[]} Array of objects to be used in the prepared statement * @returns {Promise<ResultType[]>} Promise resolving after query is done. Returns an array of arrays with the result. */ const query = <ResultType extends object>(sql: string, params: any[]): Promise<ResultType[]> => { // −−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^ //Database stuff };
Then for example you could use it like this:
const result = await query<{cnt: number}>('SELECT count(*) as cnt from Table id=?', [2]); // −−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^ return result[0].cnt;
But here’s where #2 comes in: I wouldn’t do that except in a function you define once whose job is to tell you how many rows match:
async function getCountFromExampleTable(id: number): Promise<number> { const result = await query<{cnt: number}>('SELECT count(*) as cnt from Table id=?', [id]); return result[0].cnt; }
It’s really easy to look at getCountFromExampleTable
and know that the type argument we’re giving ({cnt: number}
) is correct. Then any code that needs to do this can reuse getCountFromExampleTable
rather than having to provide that type argument (and possibly get it wrong).
You’d probably want getCountFromExampleTable
to be more generic, perhaps accepting the table name and the parameters, but you get the idea. (If so, be sure to test the table name to ensure it can’t be used as part of an SQL injection attack, and continue to use a prepared statement as you already are.)