Skip to content

nathanhleung/protobuf-ts-types

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

protobuf-ts-types

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

How it Works

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.

Usage

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)
// ```

Limitations

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

API

pbt

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.

Returns

  • 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.

About

🛫 Zero-codegen, no-compile TypeScript `type` inference from protobuf `message`s

Resources

Stars

Watchers

Forks