Repository navigation
Migrate the generated TypeScript bindings from ts-proto to protobuf-es v2 #3632
Description
Summary
Migrate the generated TypeScript bindings of the control plane API from ts-proto to protobuf-es v2 (@bufbuild/protobuf v2). Use Connect-ES v2 (@connectrpc/connect v2) for the clients.
Current state
app/controlplane/api/buf.gen.yaml generates the TypeScript bindings with ts-proto:
- remote: buf.build/community/stephenh-ts-proto:v1.151.1
out: ./gen/frontend
opt:
- outputClientImpl=grpc-web
- esModuleInterop=true
- useOptionals=messages- The output goes to
app/controlplane/api/gen/frontend. - The output contains the control plane, attestation, workflow contract,
errorsandjsonfilterprotos. - The output also contains third-party imported protos. Examples:
protoc-gen-openapiv2/options/annotations.ts,protoc-gen-openapiv2/options/openapiv2.ts,buf/validate/...andgoogle/api/.... - The generated code depends on
@improbable-eng/grpc-web,browser-headersandprotobufjs/minimal.
This is the only buf.gen.yaml in the repository that emits TypeScript. The other files (app/artifact-cas/api, pkg/attestation/crafter/api, pkg/credentials/api, the plugin SDK and the conf packages) emit Go only. So the scope is this one file and its output folder.
Motivation
- ts-proto with the improbable-eng grpc-web transport is in maintenance mode.
- protobuf-es v2 and Connect-ES v2 are the stack that Buf maintains. They have a smaller runtime, first-class ESM and TypeScript support, and they pass the Protobuf conformance tests.
- Some downstream consumers already generate their own protos with protobuf-es v2 into the same source tree. Today they get two different generated files for the same imported proto. For example,
protoc-gen-openapiv2/options/annotations.tsfrom ts-proto sits next toannotations_pb.tsfrom protobuf-es. The consumers must filter files out when they sync the bindings. One generator for all bindings removes this problem.
Proposed implementation
1. Replace the generator plugin
protobuf-es v2 also generates the service descriptors. A separate connect-es plugin is not necessary.
- remote: buf.build/bufbuild/es:v2.16.0
out: ./gen/frontend
opt: target=ts
include_imports: trueinclude_imports: true generates the imported dependencies (openapiv2 options, buf/validate, google/api) as *_pb.ts files. Consumers then get a self-contained set of files.
2. Changes in the generated code
- File names change to
<name>_pb.ts. - Messages are plain types. Create them with
create(FooSchema, {...}). - Serialize with
toBinary,fromBinary,toJsonandfromJsonfrom@bufbuild/protobuf. - Enums are TypeScript enums.
- Oneofs use the
{ case, value }form. - Use
@bufbuild/protobuf/wktforTimestamp(timestampDate,timestampFromDate).
3. Clients
- Create clients with
createClient(Service, transport)from@connectrpc/connectv2. - Use
createGrpcWebTransportfrom@connectrpc/connect-webv2. The transport stays gRPC-web, so the server needs no change. - Remove the
@improbable-eng/grpc-web,browser-headersandprotobufjsdependencies.
import { createClient } from "@connectrpc/connect";
import { createGrpcWebTransport } from "@connectrpc/connect-web";
import { WorkflowService } from "./controlplane/v1/workflow_pb";
const transport = createGrpcWebTransport({ baseUrl: "https://cp.example.com" });
const client = createClient(WorkflowService, transport);4. Transition
- This is a breaking change for every consumer of
gen/frontend. - If a transition period is necessary, emit the protobuf-es output to a new folder first (for example
gen/frontend-es). Keep the ts-proto output ingen/frontenduntil the consumers migrate. Then remove the ts-proto plugin.
5. Regeneration and CI
- Run
make apifrom the repository root (it runsbuf generateinapp/controlplane/api) and commit the new output. - Remove the old ts-proto files from the output folder.
buf generatedoes not delete stale files. - The
test.ymlworkflow runsmake apiand fails if the tree is dirty. The regenerated files must be in the same pull request.
Acceptance criteria
app/controlplane/api/buf.gen.yamlusesbuf.build/bufbuild/esv2 withtarget=tsandinclude_imports: true.- The generated output has no imports of
@improbable-eng/grpc-web,browser-headersorprotobufjs. - The generated output contains the imported third-party protos as
*_pb.tsfiles. make apiproduces no diff in CI.- The release notes state the breaking change for consumers of
gen/frontend.
🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri
Activity