在TypeScript上实现一个动态惰性代理集合类

wbrvyc0a  于 2023-05-30  发布在  TypeScript
关注(0)|答案(1)|浏览(195)

我正在尝试在typescript中实现一个惰性数据库连接集合。我正在创建一个名为DatabaseCollection的类,我的想法是使用代理来延迟加载连接(我使用knex作为连接器),这个类在节点上下文方面工作得很好,但Typescript说“属性不存在”,我不知道如何告诉Typescript动态属性类。
我使用了一个转换为Record<string, Knex>的变通方法,但我想正确地实现它,提前感谢。
下面是代码:

const debug = true
process.env['DB_TEST_URL'] = 'mysql://root@localhost/test';

class NotImportantConnector {
    public url: string;

    constructor(url: string) {
        this.url = url;
    }
}

function dbFromURL(url: string): NotImportantConnector {
    return new NotImportantConnector(url);
}

class DatabasesCollection<T> {
    protected databases: Record<string, T> = {};

    constructor() {
        return new Proxy(this, this) as any;
    }

    get (target: DatabasesCollection<T>, name: string): T {
        if (name in target.databases) {
            return target.databases[name];
        }

        const envName = `DB_${name.replace(/[A-Z]/g, l => `_${l}`).toUpperCase()}_URL`;

        if (!process.env[envName]) {
            throw new Error(`Call to database ${name} needs ${envName} environment variable to run`);
        }

        target.databases[name] = dbFromURL(process.env[envName] as string) as T;
        return target.databases[name];
    }
}

// Not working with error Property 'test' does not exist on type 'DatabasesCollection<NotImportantConnector>'.
// const db = new DatabasesCollection<NotImportantConnector>();

// Workaround, using as 
const db = new DatabasesCollection<NotImportantConnector>() as unknown as Record<string, NotImportantConnector>;

console.log(db.test);
gz5pxeao

gz5pxeao1#

TypeScript不会让使用class声明来使DatabasesCollection<T>按照您想要的方式运行变得容易,也就是说,每个示例都应该具有类的所有已知属性,加上索引签名,其中每个其他属性键都具有T类型的值。但是TypeScript不能直接表示这种“每隔一个属性”的概念;在microsoft/TypeScript#17867上有一个长期的开放特性请求,但它还不是语言的一部分。因此,直接向类添加索引签名不会完全按照预期的方式进行。参见How to define Typescript type as a dictionary of strings but with one numeric "id" property,了解一般的各种替代方法。
对于您的用例,可以将DatabasesCollection<T>的示例作为已知示例类型与{[k: string]: T}(又名Record<string, T>)的交集。这在访问该类型的值时表现得足够好…尽管很难以编译器认为是类型安全的方式实际 * 生成 * 该类型的值。
这意味着我们需要使用类似类型Assert的东西来说服编译器,你的类构造函数产生了该类型的示例。
为此,我建议将DatabasesCollection<T>类重新命名为_DatabasesCollection<T>,这样我们就可以使用名称DatabasesCollection作为所需示例类型的名称(_DatabasesCollection<T> & Record<string, T>),以及Assert的类构造函数的名称。像这样:

class _DatabasesCollection<T> {
  ⋯ // same impl, more or less
}

type DatabasesCollection<T> = _DatabasesCollection<T> & Record<string, T>;
const DatabasesCollection = _DatabasesCollection as new <T>() => DatabasesCollection<T>;

让我们来测试一下:

const db = new DatabasesCollection<NotImportantConnector>();
// const db: DatabasesCollection<NotImportantConnector>

const t = db.test;
// const t: NotImportantConnector

看起来不错
Playground链接到代码

相关问题