Zero-codegen, no-compile TypeScript
type
inference from protobufmessage
s.
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
string
sas const
, probably requires ats-patch
compiler patch to import.proto
files until microsoft/TypeScript#42219 is resolved service
s andrpc
s are not supported (onlymessage
s)oneof
andmap
fields are not supportedimport
s 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 message
s 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 knownmessage
, the returned type is the inferred type of the givenMessageName
. - If
MessageName
is not a knownmessage
, the returned type isnever
.