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.
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";
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.