procframe

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2026 License: MIT Imports: 4 Imported by: 0

README

procframe

Proto-driven typed handler runtime for Go. Define procedures in Protocol Buffers, generate transport glue, and write only the handler.

One handler serves CLI, Connect/gRPC, and WebSocket. The same generic middleware model covers all 4 RPC shapes: unary, client-stream, server-stream, and bidi.

Key Features

  • Use .proto files as the single source of truth for procedure schemas and config.
  • Run the same typed handler on CLI, Connect/gRPC, and WebSocket.
  • Support all 4 RPC shapes: unary, client-stream, server-stream, and bidirectional streaming.
  • Compose conn-based middleware around a single Conn interface with Receive and Send.
  • Generate handler interfaces, the CLI command tree, flag parsing, Connect/gRPC handlers, WebSocket session handlers, and config loading.
  • Support both human and agent workflows with flat flags for humans and --json or stdin NDJSON for agents.
  • Map canonical error codes to exit codes, Connect/gRPC status codes, and WebSocket error frames.
  • Keep result data on stdout and diagnostics on stderr.

Transports

  • CLI uses flags or --json for unary and server-stream procedures, and stdin NDJSON for client-stream and bidi procedures.
  • Connect/gRPC runs over HTTP. Bidi requires HTTP/2.
  • WebSocket uses a JSON session protocol with open, message, and close frames.

All transports support all 4 RPC shapes (unary, client-stream, server-stream, bidi).

Installation

go get github.com/shuymn/procframe

Proto options (add to your buf.yaml deps):

deps:
  - buf.build/procframe/proto

Code generation plugin:

go install github.com/shuymn/procframe/cmd/protoc-gen-procframe-go@latest

Add to your buf.gen.yaml:

version: v2
plugins:
  - local: protoc-gen-go
    out: gen
    opt: paths=source_relative
  - local: protoc-gen-procframe-go
    out: gen
    opt:
      - paths=source_relative
      # - config_proto=path/to/config.proto  # default: any file named config.proto
Requirements
  • Go 1.25+
  • buf or protoc (for proto code generation)

Contributing

Development requires additional tooling:

  • Task is the task runner. Use task check for full verification.
  • lefthook manages Git hooks. Run lefthook install after cloning.
task              # list all available tasks
task check        # lint + proto + build + test + tidy
task test         # test with race detection

License

MIT

Documentation

Overview

Package procframe provides a proto-driven typed handler runtime.

It defines transport-independent abstractions for requests, responses, streaming, and handlers across all four RPC shapes (unary, client-stream, server-stream, bidi). Procedure handlers return ordinary Go errors; transports map those errors to structured statuses at the boundary. Code generation from proto definitions produces handler interfaces and transport glue for CLI, Connect/gRPC (HTTP), and WebSocket; this package supplies the minimal runtime kernel that the generated code builds on.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func InvokeBidi

func InvokeBidi[Req, Res any](
	ctx context.Context,
	spec CallSpec,
	stream BidiStream[Req, Res],
	handler func(context.Context, BidiStream[Req, Res]) error,
	interceptors ...Interceptor,
) error

InvokeBidi executes a typed bidi-stream handler through the interceptor chain.

func InvokeServerStream

func InvokeServerStream[Req, Res any](
	ctx context.Context,
	spec CallSpec,
	req *Request[Req],
	stream ServerStream[Res],
	handler func(context.Context, *Request[Req], ServerStream[Res]) error,
	interceptors ...Interceptor,
) error

InvokeServerStream executes a typed server-stream handler through the interceptor chain.

Types

type AnyRequest

type AnyRequest interface {
	Any() any
	Meta() Meta
	Spec() CallSpec
}

AnyRequest is the middleware-facing request view.

type AnyResponse

type AnyResponse interface {
	Any() any
	Meta() Meta
}

AnyResponse is the middleware-facing response view.

func NewAnyResponse

func NewAnyResponse(msg any) AnyResponse

NewAnyResponse constructs a middleware-visible response from an arbitrary message.

func NewAnyResponseWithMeta

func NewAnyResponseWithMeta(msg any, meta Meta) AnyResponse

NewAnyResponseWithMeta constructs a middleware-visible response with explicit metadata.

type BidiStream

type BidiStream[Req, Res any] interface {
	Context() context.Context
	Receive() (*Request[Req], error)
	Send(*Response[Res]) error
}

BidiStream is the server-side interface for bidirectional streaming. It supports both receiving requests and sending responses concurrently. Concrete implementations are provided by each transport.

type BidiStreamHandler

type BidiStreamHandler[Req, Res any] interface {
	HandleBidi(context.Context, BidiStream[Req, Res]) error
}

BidiStreamHandler handles a bidirectional stream of requests and responses.

type CallShape

type CallShape string

CallShape identifies the RPC shape for a handler call.

const (
	CallShapeUnary        CallShape = "unary"
	CallShapeClientStream CallShape = "client_stream"
	CallShapeServerStream CallShape = "server_stream"
	CallShapeBidi         CallShape = "bidi"
)

type CallSpec

type CallSpec struct {
	Procedure string
	Transport Transport
	Shape     CallShape
}

CallSpec describes a transport call seen by middleware.

type ClientStream

type ClientStream[Req any] interface {
	Context() context.Context
	Receive() (*Request[Req], error)
}

ClientStream is the server-side interface for receiving a sequence of requests from the caller. Concrete implementations are provided by each transport.

type ClientStreamHandler

type ClientStreamHandler[Req, Res any] interface {
	Handle(context.Context, ClientStream[Req]) (*Response[Res], error)
}

ClientStreamHandler handles a stream of requests and returns a single response.

type Code

type Code string

Code represents a canonical error code used across all transports.

const (
	CodeInvalidArgument  Code = "invalid_argument"
	CodeNotFound         Code = "not_found"
	CodeInternal         Code = "internal"
	CodeUnauthenticated  Code = "unauthenticated"
	CodeUnavailable      Code = "unavailable"
	CodeAlreadyExists    Code = "already_exists"
	CodePermissionDenied Code = "permission_denied"
	CodeConflict         Code = "conflict"
)

func CodeOf

func CodeOf(err error) (Code, bool)

CodeOf extracts the Code from an error chain. It returns the code and true if the chain contains a StatusError, or ("", false) otherwise.

type Conn

type Conn interface {
	Context() context.Context
	Spec() CallSpec
	Receive() (AnyRequest, error)
	Send(AnyResponse) error
}

Conn is the generic connection surface seen by middleware. It provides shape-independent Receive/Send operations. Middleware composes behavior by wrapping a Conn and delegating Receive/Send to the inner Conn (the conn-decorator pattern).

type ErrorMapper

type ErrorMapper func(error) (*Status, bool)

ErrorMapper maps an error to a transport-facing Status. It returns false when the error should remain unclassified.

type HandlerFunc

type HandlerFunc func(context.Context, Conn) error

HandlerFunc is the generic handler invocation shape used by middleware.

type Interceptor

type Interceptor interface {
	Wrap(HandlerFunc) HandlerFunc
}

Interceptor composes cross-cutting behavior around handler execution.

type InterceptorFunc

type InterceptorFunc func(HandlerFunc) HandlerFunc

InterceptorFunc is a function that implements Interceptor.

func (InterceptorFunc) Wrap

type Meta

type Meta struct {
	Procedure string
	RequestID string
	SessionID string
	Labels    map[string]string
}

Meta carries transport-independent metadata for a procedure call.

type Request

type Request[T any] struct {
	Msg  *T
	Meta Meta
}

Request wraps a typed message with transport-independent metadata.

type Response

type Response[T any] struct {
	Msg  *T
	Meta Meta
}

Response wraps a typed message with transport-independent metadata.

func InvokeClientStream

func InvokeClientStream[Req, Res any](
	ctx context.Context,
	spec CallSpec,
	stream ClientStream[Req],
	handler func(context.Context, ClientStream[Req]) (*Response[Res], error),
	interceptors ...Interceptor,
) (*Response[Res], error)

InvokeClientStream executes a typed client-stream handler through the interceptor chain.

func InvokeUnary

func InvokeUnary[Req, Res any](
	ctx context.Context,
	spec CallSpec,
	req *Request[Req],
	handler func(context.Context, *Request[Req]) (*Response[Res], error),
	interceptors ...Interceptor,
) (*Response[Res], error)

InvokeUnary executes a typed unary handler through the interceptor chain.

type ServerStream

type ServerStream[Res any] interface {
	Context() context.Context
	Send(*Response[Res]) error
}

ServerStream is the server-side interface for sending a sequence of responses back to the caller. Concrete implementations are provided by each transport (e.g. transport/cli).

type ServerStreamHandler

type ServerStreamHandler[Req, Res any] interface {
	HandleStream(context.Context, *Request[Req], ServerStream[Res]) error
}

ServerStreamHandler handles a single request and writes zero or more responses to the provided ServerStream.

type Status

type Status struct {
	Code      Code
	Message   string
	Retryable bool
}

Status is the transport-facing error metadata produced at boundaries.

func StatusOf

func StatusOf(err error) (*Status, bool)

StatusOf extracts a Status from an error chain. It returns false when the chain does not contain a StatusError.

type StatusError

type StatusError struct {
	// contains filtered or unexported fields
}

StatusError is the default structured error wrapper provided by procframe.

func Errorf

func Errorf(code Code, format string, args ...any) *StatusError

Errorf creates a StatusError with a formatted message.

func NewError

func NewError(code Code, msg string) *StatusError

NewError creates a StatusError with the given code and message.

func WrapError

func WrapError(code Code, msg string, cause error) *StatusError

WrapError creates a StatusError that wraps a cause error.

func (*StatusError) Code

func (e *StatusError) Code() Code

Code returns the canonical error code.

func (*StatusError) Error

func (e *StatusError) Error() string

Error returns a string in the form "<code>: <message>". The cause is intentionally omitted; use errors.Unwrap to traverse the chain.

func (*StatusError) IsRetryable

func (e *StatusError) IsRetryable() bool

IsRetryable reports whether the caller may retry the operation.

func (*StatusError) Message

func (e *StatusError) Message() string

Message returns the human-readable error message.

func (*StatusError) Status

func (e *StatusError) Status() *Status

Status returns the transport-facing status carried by the error.

func (*StatusError) Unwrap

func (e *StatusError) Unwrap() error

Unwrap returns the underlying cause so that errors.Is and errors.As work through the error chain.

func (*StatusError) WithRetryable

func (e *StatusError) WithRetryable() *StatusError

WithRetryable returns a copy of e with the retryable flag set to true.

type Transport

type Transport string

Transport identifies the boundary executing a handler call.

const (
	TransportCLI     Transport = "cli"
	TransportConnect Transport = "connect"
	TransportWS      Transport = "ws"
)

type UnaryHandler

type UnaryHandler[Req, Res any] interface {
	Handle(context.Context, *Request[Req]) (*Response[Res], error)
}

UnaryHandler handles a single request and returns a single response.

Directories

Path Synopsis
cmd
protoc-gen-procframe-go command
Command protoc-gen-procframe-go is a protoc plugin that generates handler interfaces and CLI runner code from service definitions annotated with procframe options.
Command protoc-gen-procframe-go is a protoc plugin that generates handler interfaces and CLI runner code from service definitions annotated with procframe options.
examples
gen
internal
codegen
Package gen implements code generation for protoc-gen-procframe-go.
Package gen implements code generation for protoc-gen-procframe-go.
transport
cli
connect
Package connect provides a Connect protocol transport for procframe services.
Package connect provides a Connect protocol transport for procframe services.
ws

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL