typescripttypescript-typingstypescript-generics

Regarding TypeScript generics, is T[number][] equal to T?


I'm writing a TypeScript method which takes a generic, T, which extends an array of a mysql result. I map through a method which returns Promises of elements of T (T[number]), and then await them with a Promise.all. This should result in an array of the elements of T, T[number][], which I would have thought would simplify to T. But when I return my results, I get an error Type 'T[number][]' is not assignable to type 'T'.

What am I doing wrong here? Is T[number][] not equal to T?

Here is a simplified ts playground reproduction.

export async function executeAllParallel<
  T extends readonly (QueryResult | Record<string | number, unknown>[])[],
>(
  dataAccountId: string,
  queries: QueryOptions[],
  retries: number = 0,
): Promise<T> {
  const connection: mysql.PoolConnection =
    await poolCluster.getConnection(dataAccountId);
  try {
    await connection.beginTransaction();

    const results: T[number][] = await Promise.all(
      queries.map((query) =>
        connection.execute<T[number]>(query).then((result) => result[0]),
      ),
    );

    await connection.commit();

    // Type 'T[number][]' is not assignable to type 'T'.
    return results;
  } catch (error) {
    await connection.rollback();
    if (retries <= 1) throw error;
    connection.release();
    return executeAllParallel(dataAccountId, queries, retries - 1);
  } finally {
    connection.release();
  }
}

Solution

  • TypeScript can express, and I'm assuming you are making use of, tuple types.

    A tuple type is another sort of Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.

    As an example, consider this T:

    type SpecificArray = [number, boolean, string];
    

    This defines an array with exactly three elements of those particular types.

    type T1 = T[number];
    

    The best the compiler can do is infer T1 = number | boolean | string.

    type T2 = T1[];
    

    That's (number | boolean | string)[], the information on individual element restrictions has been lost.

    So no, T[number][] won't be T.


    Overall, your code assumes the individual queries will return the types satisfying the corresponding indices of T, and unless you want to further restrict the types of QueryOptions, the best you can do is cast results to T manually.