Show HN:无需代码生成,无编译即可从 Protobuf 推断 TypeScript 类型
Show HN: Zero-codegen, no-compile TypeScript type inference from Protobufs

原始链接: https://github.com/nathanhleung/protobuf-ts-types

`protobuf-ts-types` 库无需代码生成即可直接从 protobuf 消息定义推断 TypeScript 类型。这个概念验证库使用 TypeScript 的模板字面量类型来解析 `.proto` 文件并创建相应的 TypeScript 接口。 安装软件包后,您可以导入 `pbt` 对象并使用 `pbt.infer` 来提取类型。将 proto 字符串作为 `pbt.infer` 的泛型参数提供。您可以指定特定消息名称来检索其类型,或者省略它以获取所有消息名称与其类型的映射。推断出的类型反映了 proto 结构,包括字段类型和可选字段。这使得可以在您的 TypeScript 代码中安全地使用 protobuf 消息。 局限性包括:不支持服务、RPC、`oneof`、`map` 字段和导入。使用 `.proto` 文件需要 ts-patch 编译器补丁。

Hacker News 最新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Show HN:无需代码生成,无需编译,直接从 Protobuf 推断 TypeScript 类型 (github.com/nathanhleung) 18nleung 49 分钟前 12 分 | 隐藏 | 过去 | 收藏 | 讨论 加入我们 6 月 16-17 日在旧金山举办的 AI 初创公司学校! 指导原则 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系我们 搜索:

原文

Zero-codegen, no-compile TypeScript type inference from protobuf messages.

protobuf-ts-types lets you define language-agnostic message types in proto format, then infers TypeScript types from them with no additional codegen.

Try on github.dev | View on CodeSandbox

Warning

Proof of concept, not production ready. See Limitations below for more details.

Screenshot

In short, aggressive use of TypeScript's template literal types. Annotated example from the source:

// Pass the proto string you want to infer `message` names from as a generic parameter
type MessageNames<Proto extends string> =
  // Infer `message` parts using template literal type
  WrapWithNewlines<Proto> extends `${string}${Whitespace}message${Whitespace}${infer MessageName}${OptionalWhitespace}{${string}}${infer Rest}`
    ? // Recursively infer remaining message names
      [MessageName, ...MessageNames<Rest>]
    : [];

See more in src/proto.ts.

First, install the package.

npm install https://github.com/nathanhleung/protobuf-ts-types

Then, use it in TypeScript.

import { pbt } from "protobuf-ts-types";

const proto = `
    syntax = "proto3";

    message Person {
      string name = 1;
      int32 id = 2;
      bool is_ceo = 3;
      optional string description = 4;
    }

    message Group {
        string name = 1;
        repeated Person people = 2;
    }
`;

// `Proto` is a mapping of message names to message types, inferred from the
// `proto` source string above.
type Proto = pbt.infer<typeof proto>;

type Person = Proto["Person"];
type Person2 = pbt.infer<typeof proto, "Person">;

// `Person` and `Person2` are the same type:
// ```
// {
//     name: string;
//     id: number;
//     is_ceo: boolean;
//     description?: string;
// }
// ```

type Group = pbt.infer<typeof proto, "Group">;

function greetPerson(person: Person) {
  console.log(`Hello, ${person.name}!`);

  if (person.description) {
    console.log(`${person.description}`);
  } else {
    console.log("(no description)");
  }
}

function greetGroup(group: Group) {
  console.log(`=========${"=".repeat(group.name.length)}===`);
  console.log(`= Hello, ${group.name}! =`);
  console.log(`=========${"=".repeat(group.name.length)}===`);

  for (const person of group.people) {
    greetPerson(person);
    console.log();
  }
}

// If the structure of the `Group` or any of the individual `Person`s does not
// match the type, TypeScript will show an error.
greetGroup({
  name: "Hooli",
  people: [
    {
      name: "Gavin Belson",
      id: 0,
      is_ceo: true,
      description: "CEO of Hooli",
    },
    {
      name: "Richard Hendricks",
      id: 1,
      is_ceo: true,
      description: "CEO of Pied Piper",
    },
    {
      name: "Dinesh Chugtai",
      id: 2,
      is_ceo: false,
      description: "Software Engineer",
    },
    {
      name: "Jared Dunn",
      id: 3,
      is_ceo: false,
    },
  ],
});

// Output:
// ```
// =================
// = Hello, Hooli! =
// =================
// Hello, Gavin Belson!
// CEO of Hooli

// Hello, Richard Hendricks!
// CEO of Pied Piper

// Hello, Dinesh Chugtai!
// Software Engineer

// Hello, Jared Dunn!
// (no description)
// ```
  • If not using inline (i.e., literals in TypeScript) proto strings as const, probably requires a ts-patch compiler patch to import .proto files until microsoft/TypeScript#42219 is resolved
  • services and rpcs are not supported (only messages)
  • oneof and map fields are not supported
  • imports are not supported (for now, concatenate)

Top-level exported namespace.

import { pbt } from "protobuf-ts-types";

pbt.infer<Proto extends string, MessageName extends string = "">

Given a proto source string, infers the types of the messages in the source.

  • If MessageName is an empty string, the returned type is a mapping from message names to message types.
  • If MessageName is a known message, the returned type is the inferred type of the given MessageName.
  • If MessageName is not a known message, the returned type is never.
联系我们 contact @ memedata.com