简体   繁体   English

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

[英]Compile TypeScript Types for runtime use

I have a client server application that communicates using REST calls.我有一个使用 REST 调用进行通信的客户端服务器应用程序。

To prevent that I accedently use the wrong types I defined all RestCalls in a common file (excerpt):为了防止我意外地使用了错误的类型,我在一个公共文件中定义了所有 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

The url and method are encoded in the string, it also uses express url parameters ( /: ) . url 和方法编码在字符串中,它还使用表达 url 参数/: :)。 Set defines the data in the body and if the server should check authentication Set定义正文中的数据以及服务器是否应检查身份验证

  1. request要求
  2. response回复
  3. response on error错误响应
  4. if authentication is needed如果需要认证
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
};

I can then use following types to get the correct values然后我可以使用以下类型来获取正确的值

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;

I would now like to know at runtime if for a specific call authentication is required.我现在想在运行时知道是否需要特定的呼叫身份验证。

I could encode it in the url like I did for the method.我可以在 url 中对其进行编码,就像我对方法所做的那样。 Or use NeedsAuthentication as a paramter where the url is also provided.或者使用NeedsAuthentication作为参数,其中还提供了 url。 That way when I put in the URL I get autocomplete for that will only have true if authentication is needed otherwise false.这样,当我放入 URL 时,我会获得自动完成功能,因为只有在需要身份验证时才会显示为真,否则为假。

I don't like both workarounds.我不喜欢这两种解决方法。

What I would like to do is我想做的是

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

Is there any way to tell the compiler to compile the types in the output JS so I cann do the same thing the compiler does when interfereing what a parameter needs to be?有什么方法可以告诉编译器编译 output JS 中的类型,这样我就不能做编译器在干扰参数需要时所做的同样事情?

My only other solution currently is writing a script that is executed prebuild which parses my definition and put out a map that contains for every URL if it needs authentication using some regex parsing...我目前唯一的其他解决方案是编写一个执行 prebuild 的脚本,它解析我的定义并输出一个 map,如果它需要使用一些正则表达式解析进行身份验证,则包含每个 URL...


EDIT编辑

I would like to implement following function我想实现以下 function

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

which is not part of the transmited data but encoded in the type mapping.它不是传输数据的一部分,而是编码在类型映射中。

The compiler will nicely map it to true or false编译器会很好地将 map 设置为 true 或 false 样品完成 1 样品完成 2 for const strings on compieltime (the actual value would still not be emiited...)对于编译时的 const 字符串(实际值仍然不会被发出......) 样品完成 3

I could try to use the typescript compiler and call whatever function evaluates the return values...我可以尝试使用 typescript 编译器并调用任何 function 评估返回值...

No. Typescript types will be emitted during the compilation phase.编号 Typescript 类型将在编译阶段发出。

Your options are:您的选择是:

  1. Use JSON-Schema (Ajv) to validat the input of incoming http json requests: https://github.com/ajv-validator/ajv使用 JSON-Schema (Ajv) 验证传入 http json 请求的输入: https://github.com/ajv-validator/ajv
  2. Use Swagger (Almost the same as (1)).使用 Swagger(与(1)几乎相同)。
  3. Use a validator which works with your framework.使用适用于您的框架的验证器。
  4. I found this project which tries to create runtime asserts from typescript types: https://github.com/skunkteam/types .我发现这个项目试图从 typescript 类型创建运行时断言: https://github.com/skunkteam/types But I never used it my self.但我自己从来没有使用过它。
  5. https://github.com/nanoporetech/ts-runtime-typecheck - Same as (4) but never used it as well. https://github.com/nanoporetech/ts-runtime-typecheck - 与 (4) 相同,但也从未使用过。

I created an script that runs pre build and uses the TypeScript compilers typechecker to infer the retunrtype of the function for every possible input.我创建了一个运行预构建的脚本,并使用 TypeScript 编译器类型检查器来推断每个可能输入的 function 的 retunrtype。 Then this is emited to a file which then is used by the function.然后将其发送到一个文件,然后由 function 使用。

script脚本

    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);

In my code I use在我的代码中我使用

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

Its not the fastest way to do it but it works...它不是最快的方法,但它有效......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM