1

我刚刚开始在一个新项目中使用 io-ts 而不是 runtypes。我用于配置验证的模式是使用配置的每个部分的类型创建一个对象;

const configTypeMap = {
    jwtSecret: t.string,
    environment: t.string,
    postgres: postgresConnectionType
} as const

type Config = { [Key in keyof typeof configTypeMap]: t.TypeOf<typeof configTypeMap[Key]> }

以及另一个具有应满足该类型的值的对象;

const envVarMap = {
    jwtSecret: process.env.JWT_SECRET,
    environment: process.env.ENVIRONMENT,
    postgres: {
        user: process.env.POSTGRES_USER,
        password: process.env.POSTGRES_PASSWORD,
        host: process.env.POSTGRES_HOST,
        port: process.env.POSTGRES_PORT,
        database: process.env.POSTGRES_DATABASE,
    }
} as const

然后我创建一个函数,该函数接受一个键并返回该键下经过验证的配置片段;

const getConfig = <T extends keyof Config>(key: T): Config[T] => {
    const result: Either<t.Errors, Config[T]>  = configTypeMap[key].decode(envVarMap[key])
    if (isLeft(result)) {
        throw new Error(`Missing config: ${key}`)
    }

    return result.right
}

这在运行类型中运行良好(尽管看起来有点不同)。但是,在 io-ts 中,configTypeMap[key].decode推断为;

Left<t.Errors> | Right<string> | Right<{
    user: string;
    password: string;
    host: string;
    port: string;
    database: string;
}>

它丢失了有关从哪个键访问解码功能的所有上下文。我可以result转换回正确的类型Either<t.errors, Config[T]>,但我想要一种方法来做到这一点,而无需转换来验证我不只是忽略错误。

编辑:

在操场上玩耍,我设法得到了一个不涉及 io-ts 的复制示例,所以我认为这只是我对打字稿推理的理解的问题。我仍然想找到一种方法来结束具有签名的函数<T extends keyof Config>(key: T): Config[T]

4

2 回答 2

0

我认为您是正确的,这是 TypeScript 的一种限制,因为如果您使用相同的键对它们进行索引,它无法正确判断这两个接口将具有对齐的类型。但是,如果您将查找与后续函数调用分开,TypeScript 可以解决这个问题。

这种 curried 函数使得 TypeScript 可以始终遵循这些类型:

import * as t from 'io-ts';
import * as E from 'fp-ts/lib/Either';

function makeConfigValidator<A>(codecs: {[K in keyof A]: t.Type<A[K]> }) {
    return <K extends keyof A>(key: K) => (
        config: {[ConfigKey in keyof A]: unknown },
    ): A[K] => {
        const validated = codecs[key].decode(config);
        if (E.isLeft(validated)) {
            throw new Error('invalid');
        }
        return validated.right;
    };
}

const jwtSecret: string = makeConfigValidator(configTypeMap)('jwtSecret')(envVarMap);
console.log(makeConfigValidator(configTypeMap)('postgres')(envVarMap).port)

完整的游乐场

存储中间值以避免重复自己可能是有意义的。例如:

const configValidator = makeConfigValidator(configTypeMap);
const jwtSecretValidator = configValidator("jwtSecret");
const secret: string = jwtSecretValidator(envVarMap);
于 2021-10-11T11:06:04.013 回答
0

通过删除Config类型并将get函数的返回类型设置为Static<typeof config[T]>,可以获得所需的响应。我不知道您是否需要其他Config类型的类型,或者这是否可以使用,io-ts但这可能会解决您的问题。

是一个带有示例的更新游乐场。

于 2020-10-09T20:11:41.070 回答