Search code examples
javascriptmysqltypescriptmysql2

If the mysql result value is an array and generic T is not an array, can it be removed in typescript?


type User = {
  name: string
  email: string
}

This is my code,

import type { PoolConnection, RowDataPacket, OkPacket } from "mysql2/promise";

type dbDefaults = RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[];
type dbQuery<T> = T & dbDefaults;

type FlattenIfArray<T> = T extends (infer R)[] ? R : T;

function isFlattenArray<T>(rows: any[]): rows is T[] {
  return rows.length < 1;
}

// eslint-disable-next-line consistent-return
export async function queryWrapper<T>(
  { query, values }: { query: string; values?: string[] },
  conn: PoolConnection
): Promise<T | undefined> {
  try {
    await conn.beginTransaction();
    const [rows, _] = await conn.query<dbQuery<T>>(query, values || undefined);
    await conn.commit();

    if (isFlattenArray<FlattenIfArray<T>>(rows)) {
      return rows;
    }

    return rows;
  } catch (error) {
    await conn.rollback();
  } finally {
    conn.release();
  }
}

Mysql code only returns the array

There is no problem with User[]. But When using User, I want to remove the arrangement.

So I used this code, but it didn't work. What should I do?



I added additional explanation.

const result = queryWrapper<User>(query, values, conn)
When use user, It comes out like this.

[
  {
   name: "user-name",
   email: "[email protected]"
  }
]

But I want it comes out like this.

{
 name: "user-name",
 email: "[email protected]"
}

Solution

  • I'll reply by myself.

    type dbDefaults =
      | RowDataPacket[]
      | RowDataPacket[][]
      | OkPacket
      | OkPacket[]
      | ResultSetHeader;
    
    type Query = { query: string; values?: any[] };
    
    type QueryFunction<T = any> = () => Promise<[T & dbDefaults, FieldPacket[]]>;
    
    type AlwaysArray<T> = T extends (infer R)[] ? R[] : T[];
    // eslint-disable-next-line consistent-return
    export async function queryTransactionWrapper<T = any>(
      queries: QueryFunction[],
      conn: PoolConnection
    ): Promise<[AlwaysArray<T>, FieldPacket[]][] | undefined> {
      try {
        await conn.beginTransaction();
        // await conn.query("START TRANSACTION;");
    
        const executedQueries = await Promise.all(
          queries.map((query) => {
            return query();
          })
        );
    
        // await conn.query("COMMIT;");
        await conn.commit();
        return executedQueries;
      } catch (error) {
        logger.error(colors.blue(JSON.stringify(error)));
        await conn.rollback();
      } finally {
        conn.release();
      }
    }
    
    export function findOne({ query, values }: Query, conn: PoolConnection) {
      return function () {
        return conn.query<RowDataPacket[]>(query, values);
      };
    }
    
    export function find({ query, values }: Query, conn: PoolConnection) {
      return function () {
        return conn.query<RowDataPacket[]>(query, values);
      };
    }
    
    export function update({ query, values }: Query, conn: PoolConnection) {
      return function () {
        return conn.query<ResultSetHeader>(query, values);
      };
    }
    
    export function insert({ query, values }: Query, conn: PoolConnection) {
      return function () {
        return conn.query<OkPacket>(query, values);
      };
    }
    
    

    when call findOneUser

    async findByEmail(email: string): Promise<IUser | undefined> {
        const conn = await this.mysql.getConnection();
        const query = `Select * FROM ${USER_TABLE} WHERE email=?`;
    
        const findUserQueryFunction = findOne({ query, values: [email] }, conn);
    
        const executedQueries = await queryTransactionWrapper<IUser>(
          [findUserQueryFunction],
          conn
        );
        if (!executedQueries) {
          throw new Error();
        }
        const [[rows]] = executedQueries;
    
        return rows[0];
      }
    

    if <IUser[]>, findByEmail returns rows else returns rows