Proto-first PostgreSQL storage codegen for Go. You annotate a proto message and
run buf generate. The plugin then generates the storage layer for that
resource:
- canonical DDL (
CREATE TABLE, CHECKs, indexes, RLS policies, comments) - a proto/model mapper
- go-jet model and type-safe SQL builder packages
- an AIP-158 keyset-pagination schema and AIP-160 filter translator
- a string-embedded copy of the DDL for fixtures and tests
There are two more subcommands for migrations. check verifies that your
hand-written migrations still produce the schema your proto declares, and diff
scaffolds the next migration. The plugin owns the schema declaration. You own
the forward-only migrations.
Resource-oriented APIs are quick to write and hard to keep consistent. Resource
names, List/Get semantics, filters, and storage tend to drift apart over time,
and that gets worse when a coding agent writes most of the code. This plugin
ties all of that to one source of truth: the annotated proto is the schema
declaration. The mapper, go-jet types, AIP-158/160 surface, and the check
drift gate are generated from it, so the proto is the thing you review and the
agent targets while the generator handles what comes after.
The pattern isn't specific to Postgres. This plugin is just the Postgres + go-jet implementation of it.
go install github.com/redpanda-data/protoc-gen-go-jet/cmd/protoc-gen-go-jet@latestRequirements: buf, Go, and Docker. During generation the
plugin boots an ephemeral Postgres container to drive go-jet's schema
introspection, plus two more for the check / diff drift flow. Containers are
per-invocation. There's no shared dev DB.
Annotate a message with the gojet.v1 options:
import "gojet/v1/options.proto";
// LLMProvider is the stored representation of a provider config.
message LLMProvider {
option (gojet.v1.table) = {
name: "llm_providers"
primary_key: ["tenant_id", "name"]
tenancy: { column: "tenant_id", runtime_role: "app-tenant" }
default_order_by: "created_at desc"
tie_breaker: "name asc"
};
// Fields persist by default; annotate only to override or opt out.
string name = 2 [(gojet.v1.column) = { immutable: true, orderable: true }];
string display_name = 3 [(gojet.v1.column) = { orderable: true }];
string url = 11 [(gojet.v1.column) = { skip: true }]; // computed at runtime
google.protobuf.Timestamp created_at = 5 [(gojet.v1.column) = {
immutable: true, orderable: true
}];
}Wire the plugin into buf.gen.yaml, after protoc-gen-go:
version: v2
plugins:
- local: protoc-gen-go
out: gen
opt: paths=source_relative
- local: protoc-gen-go-jet
out: .
opt:
- paths=source_relative
- repo_root=.buf generate emits the full storage layer. After that, drive migrations with
the two subcommands:
# scaffold the next migration
protoc-gen-go-jet diff \
--ddl-roots=<dir-with-*_ddl.sql> \
--migrations=<your-migrations-dir> > migrations/NNNN_change.up.sql
# verify the migration chain still reproduces the declared schema
protoc-gen-go-jet check \
--ddl-roots=<dir-with-*_ddl.sql> \
--migrations=<your-migrations-dir>Run check in CI as a drift gate. It applies every migration to a fresh
Postgres and fails the build when the result diverges from the schema the proto
declares. That way a migration that forgets a proto change (or vice versa) never
merges.
- Plugin reference. The full options vocabulary, field-kind inference matrix, foreign keys, AIP-160 filtering, well-known-type mapping, nullable columns, and what the plugin refuses to generate.
- Options proto:
proto/gojet/v1/options.proto. - Worked example:
cmd/protoc-gen-go-jet/e2e/, a self-contained suite that round-trips every supported proto kind through a real Postgres testcontainer. - Agent skill:
.claude/skills/protoc-gen-go-jet/teaches an AI coding agent the migration-authoring workflow.
| Import | What |
|---|---|
cmd/protoc-gen-go-jet |
The plugin and the check / diff / from-db subcommands. |
pkg/aip |
AIP-158 keyset pagination, order-by parsing, page-token codecs. |
pkg/aip/jet |
aipjet — AIP-160 filter → go-jet BoolExpression translator and paginated execution. |
pkg/pgstore/jettypes |
Column types with custom Scan/Value (e.g. TIMESTAMPTZ[]). |
pkg/pgstore/jetgen |
Shared go-jet generator configuration. |
- protoc-gen-go-mcp exposes the same gRPC services as MCP servers. Same proto-first approach, different output.
- Request validation belongs in protovalidate (CEL rules on the proto). This plugin owns the storage contract and migration drift, not request validation.
Apache License 2.0. See LICENSE.