protoc-gen-ets: Protobuf, but make it sendable

protoc-gen-ets turns .proto schemas into ArkTS classes that OpenHarmony can move across workers without hand-written glue.

9 min read • View on GitHub • More from larksuite

A suspension bridge carries a sealed crate from a stack of protobuf blueprints on one cliff to a city of device parts on the other. The scene explains how the generator bridges familiar schemas into ArkTS code that can cross OpenHarmony thread boundaries safely.
The plugin is less a formatter than a bridge. It turns a familiar schema format into runtime-safe ArkTS.
Key Takeaways

OpenHarmony does not reward vague codegen. If a Protobuf message is going to cross a worker boundary, it has to obey ArkTS's rules, and that is where protoc-gen-ets earns its keep.

protoc plugin for ArkTS, Compile .proto files to plain ArkTS

larksuite/protoc-gen-ets (README), Project Documentation · larksuite/protoc-gen-ets README

Why this fork exists

The README says it plainly: this project was Forked from protoc-gen-ts. That is the right place to start, because the interesting move is not inventing Protobuf support from scratch. It is narrowing a general TypeScript generator until it matches OpenHarmony's flatter file layout, stricter typing, and sendable concurrency model.

Forked from protoc-gen-ts

larksuite/protoc-gen-ets (README), Project Documentation · larksuite/protoc-gen-ets README

That narrowing matters because ArkTS is not just TypeScript with a new badge. In OpenHarmony, classes that move between tasks need @Sendable, bigint mapping can change by deployment choice, and namespace shape is not cosmetic. The generator bakes those constraints into the emitted code so teams do not patch them after the fact.

A close-up of hands assembling a formal machine from interlocking shapes, gears, and paper-cut nodes. The image explains that the generator builds an abstract syntax tree first, then prints code from that structure instead of stitching strings together.
The hard part is not printing text. It is building a valid tree first.

How the pipeline works

The implementation leans on Rust and the SWC ecosystem to treat code generation like compilation, not templating. src/collector.rs walks the descriptors first and registers every message and enum. src/context.rs keeps the import graph and type registry in sync, and src/emit.rs turns the finished module items into ArkTS with swc_ecma_codegen.

The plugin's pipeline is deliberately boring in the best way. Resolve first, emit later, and let the AST enforce syntax.

That sequence is what keeps the generator honest. Cross-file references are available before emission, duplicate imports can be merged, and default-value logic in src/common/field.rs can decide whether a field belongs in the binary output or should be skipped because Proto3 still treats it as default.

What lives where

protoc \
  --plugin=protoc-gen-ets=./node_modules/.bin/protoc-gen-ets \
  --ets_out=namespace_as_file=true,bigint_as_long=true:./gen \
  --proto_path=./proto \
  ./proto/example.proto

What it buys, and what it costs

Compared with a generic TypeScript generator, protoc-gen-ets gives you less surface area to babysit and more runtime fit. Compared with handwritten ArkTS wrappers, it gives you repeatable output, consistent defaults, and a place to encode policy once. The tradeoff is obvious: this is a specialist tool. It is strongest where OpenHarmony constraints are non-negotiable, and less interesting anywhere else.

Supports ArkTS(ets) with @Sendable for concurrent

larksuite/protoc-gen-ets (README), Project Documentation · larksuite/protoc-gen-ets README
ApproachStrengthWeak spotBest fit
protoc-gen-etsArkTS output tuned for OpenHarmonySpecialized and opinionatedApps that need sendable Protobuf classes
protoc-gen-tsGeneral TypeScript generationNo OpenHarmony-specific runtime semanticsStandard TypeScript targets
Handwritten ArkTS wrappersTotal control over code shapeSlow to maintain and easy to driftTiny schemas or prototypes