编译 TypeScript 类型以供运行时使用

Compile TypeScript Types for runtime use

我有一个使用 REST 调用进行通信的客户端服务器应用程序。

为了防止我意外使用错误的类型,我在一个公共文件中定义了所有 RestCalls(摘录):

type def<TConnection extends Connections> =
    // Authentication
    TConnection extends '/auth/password/check/:login->get' ? Set<void, { found: boolean }, void, false>
    : TConnection extends '/auth/password/register->post' ? Set<RegsiterAccount<Login>, void, void, false>
    : TConnection extends '/auth/password/login->post' ? Set<Login, void, void, false>
    : TConnection extends '/auth/webauth/challenge->get' ? Set<void, {
        challenge: string,
        id: string
    }, void, false>
    : TConnection extends '/auth/webauth/register->post' ? Set<RegsiterAccount<WebAuthN> & { comment: string }, void, void, false>
    : TConnection extends '/auth/webauth/login->post' ? Set<Assertion, void, void, false>
    : TConnection extends '/auth/logout->post' ? Set<void, void, void>
    : TConnection extends '/auth/invite->get' ? Set<void, {
        link: string,
        validUntill: string
    }, void>
    : TConnection extends '/auth/invite/validate->post' ? Set<{ invite: string }, {
        granted_by: string,
        validUntill: string
    }, void, false>
    : TConnection extends '/auth/isAuthenticated->get' ? Set<void, {
        isAuthenticated: boolean,
        userName: string | undefined
    }, void, false>

    // default
    : never

url和方法在字符串中编码,它也使用express url参数(/:)Set 定义正文中的数据以及服务器是否应检查身份验证

  1. 要求
  2. 回应
  3. 错误响应
  4. 如果需要身份验证
type Set<Input extends (Object | void), result extends (Object | void), Error extends string | object | void, NeedsAuthentication extends boolean = true> = {
    input: Input, result: result,
    error: DefaultError<Error>,
    authenticated: NeedsAuthentication
};

然后我可以使用以下类型来获取正确的值

export type InputBody<TPath extends Connections> = def<TPath>['input'];
export type InputPath<TPath extends Connections> = express.RouteParameters<TPath>;
export type Input<TPath extends Connections> = InputBody<TPath> & InputPath<TPath>;


export type Result<TPath extends Connections> = def<TPath>['result']
export type NeedsAuthentication<TPath extends Connections> = def<TPath>['authenticated']

export type Error<TPath extends Connections> = def<TPath>['error']

export type Method<T extends string> = T extends `${infer path}->${infer method}`
    ? method extends METHODS ? method
    : never
    : never;
export type Path<T extends string> = T extends `${infer path}->${infer method}`

    ? method extends METHODS ? path
    : never
    : never;

我现在想知道在运行时是否需要对特定呼叫进行身份验证。

我可以像对方法所做的那样在 url 中对其进行编码。或者使用 NeedsAuthentication 作为参数,其中还提供了 url。这样,当我输入 URL 时,我会自动完成,因为只有在需要身份验证时才会有 true,否则为 false。

我不喜欢这两种解决方法。

我想做的是

const needsAuthentication :boolean = NeedsAuthentication<'/auth/webauth/login->post'>;

有什么方法可以告诉编译器编译输出 JS 中的类型,这样我就可以在干扰参数需要时做同样的事情吗?

我目前唯一的其他解决方案是编写一个脚本,该脚本在构建前执行,它解析我的定义并为每个 URL 提供一个映射,如果它需要使用一些正则表达式解析进行身份验证...


编辑

我想实现以下功能

function needsAuthentication<T extends Connections>(test:T):NeedsAuthentication<T> {
    // todo find out if authentication is actual required for this url 
}

这不是传输数据的一部分,而是编码在类型映射中。

编译器会很好地将其映射为 true 或 false 对于 compieltime 上的 const 字符串(仍然不会发出实际值......)

我可以尝试使用打字稿编译器并调用任何计算 return 值的函数...

没有。 Typescript 类型将在编译阶段发出。

您的选择是:

  1. 使用 JSON-Schema (Ajv) 验证传入 http json 请求的输入:https://github.com/ajv-validator/ajv
  2. 使用 Swagger(与 (1) 几乎相同)。
  3. 使用适用于您的框架的验证器。
  4. 我发现这个项目试图从打字稿类型创建运行时断言:https://github.com/skunkteam/types。但是我自己从来没用过。
  5. https://github.com/nanoporetech/ts-runtime-typecheck - 与 (4) 相同,但也从未使用过。

我创建了一个运行预构建的脚本,并使用 TypeScript 编译器类型检查器为每个可能的输入推断函数的 retunrtype。 然后将其发送到一个文件,然后由该函数使用。

脚本

    const project = new Project({});

    // add source files
    project.addSourceFilesAtPaths("src/**/*.ts");


    function Test(path: string) {
        const dataFile = project.createSourceFile(`src/${randomUUID()}.ts`, `import * as x from "./data" ; const check = x.needsAuthentication("${path}")`);
        const declaraiton = dataFile.getVariableDeclarationOrThrow('check');
        const result = project.getTypeChecker().getTypeText(declaraiton.getType());
        return result.toLowerCase() == 'true';
    }
    const pathChecks = paths.map(x => [x, Test(x)]).reduce((p: any, v: any) => {
        p[v[0]] = v[1];
        return p;
    }, {});

    let authenticationText = `export const lookup = ${JSON.stringify(pathChecks)} as const;`
    await fs.writeFile('src/data-authentication.g.ts', authenticationText);

在我的代码中我使用

import { lookup } from './data-authentication.g';
//...
export function needsAuthentication<T extends Connections>(test: T): NeedsAuthentication<T> {
    return lookup[test];
}

这不是最快的方法,但确实有效...