如何延迟打字稿中的通用填充 - 对于 knex 查询?

How to defer generic filling in typescript - for knex queries?

好吧,我有一个如下所示的函数,可以根据字符串标识符加载多个数据库:

function getDB<TRecord extends {} = any, TResult = unknown[]>(config_name: string, maxConnections: number = 1): Knex<TRecord, TResult> {
    if (!!cfg && !!cfg.datastores && !!cfg.datastores[config_name]) {
        if (databases[config_name]) {
            return databases[config_name];
        } else {

            const config = cfg.datastores[config_name];
            const db = knex<TRecord, TResult>({
                client: 'pg',
                connection: config,
                pool: {min: 0, max: maxConnections},
            });
            databases[config_name] = db;
            return db;
        }
    } else {
        throw new Error('config not initialized, can\'t load db');
    }
}

databases 作为 Knex<any, any>[] 类型有点不安全,但现在可以忽略。

更烦人的是在使用这个函数时我会这样使用它:

const someDB = getDB('some');
const users = someDB("users").select('username').where('id', 10);

效果很好,机械上也很流畅。然而,打字被完全忽略了。现在我注意到 knex 支持基本的打字,我想添加这个 https://knexjs.org/#typescript-support 。我天真地会这样做;

interface User {
  id: number;
  username: string;
}
const someDB = getDB('some');
const users = someDB<User>("users").select('username').where('id', 10);
//expect users to be Pick<User, username>

但是这不起作用,typescript 仍然将用户视为 any。这是因为 someDB 在调用 getDB 之后被赋予了正确的类型——这显然是 Knex<any, unknown[]>。一种解决方案是在调用 getDB:

时直接推断类型
interface User {
  id: number;
  username: string;
}
const someDB = getDB<User>('some');
const users = someDB("users").select('username').where('id', 10);
//expect users to be Pick<User, username>

然而,将代码扩展为:

interface User {
  id: number;
  username: string;
}
const someDB = getDB<User>('some');
const users = someDB("users").select('username').where('id', 10);
const students = someDB("student").select('name').where('id', 10);

这会产生错误,因为现在 students 也是 User[] 类型,显然不必如此。

那么,在调用 knex 函数之前,我将如何重构转发打字稿类型的功能?

Ts Playground

function getDB(config_name: string, maxConnections: number = 1)
   : <TRecord extends {} = any, 
      TResult = unknown[]>(
         ...params: Parameters<Knex<TRecord, TResult>>
      ) => ReturnType<Knex<TRecord, TResult>> {
   // same implementation
}

将通用参数移至 return 类型。 (是的,我知道这很疯狂,我刚发现你可以这样做)

将一个函数定义为 return 类型,接受 2 个通用参数(TRecord,TResult),然后使用 Knex(应该是一个函数)定义一个接受 Knex 参数的函数,并且return使用相同函数的 return 类型。

稍后调用 someDb<User>('users') 时应正确转发类型信息。