Repository navigation
Add stream support - #11
Conversation
The generated .proto now carries the contract interface's doc comment on the service and each method's doc comment on its rpc. The generated raw gRPC server and client interfaces repeat the method comments. Signed-off-by: Paweł Gronowski <git@grono.dev>
There was a problem hiding this comment.
🟡 Changes recommended
Client streams can hang after early server rejection, and grouped parameters can produce uncompilable adapters.
2 open findings
What changed in this PR
Adds typed server- and client-streaming support to extension contracts and generated gRPC adapters.
Changes:
- Introduces typed stream interfaces and iterator helpers.
- Extends parsing, protobuf descriptors, and wire generation for streaming.
- Adds documentation and runtime coverage.
| File | Description |
|---|---|
stream/stream.go |
Defines streaming interfaces and helpers. |
stream/stream_test.go |
Tests stream helpers. |
sdk/sdkapi/protogen/wire.gen.go |
Adds generated RPC documentation. |
sdk/sdkapi/extension.proto |
Documents the extension service. |
internal/launcher/echo/v1/protogen/wire.gen.go |
Adds generated Echo documentation. |
internal/launcher/echo/v1/echo.proto |
Documents the Echo service. |
example/greeter/v0/greeter.proto |
Documents the example service. |
docs/DESIGN.md |
Describes streaming design and semantics. |
docs/AUTHORING.md |
Documents streaming contract syntax. |
cmd/mobyextgen/wire.go |
Generates streaming gRPC adapters. |
cmd/mobyextgen/testdata/runtime_unary_contract.go.txt |
Provides unary runtime fixture. |
cmd/mobyextgen/testdata/runtime_transport_test.go.txt |
Tests transport backpressure and status handling. |
cmd/mobyextgen/testdata/runtime_server_contract.go.txt |
Provides server-streaming fixture. |
cmd/mobyextgen/testdata/runtime_contract.go.txt |
Provides mixed-method fixture. |
cmd/mobyextgen/testdata/runtime_client_contract.go.txt |
Provides client-streaming fixture. |
cmd/mobyextgen/testdata/runtime_behavior_test.go.txt |
Tests generated runtime behavior. |
cmd/mobyextgen/streaming_test.go |
Tests parsing and generation. |
cmd/mobyextgen/runtime_test.go |
Builds and tests generated packages. |
cmd/mobyextgen/runtime_race_test.go |
Enables nested race testing. |
cmd/mobyextgen/runtime_norace_test.go |
Configures non-race testing. |
cmd/mobyextgen/proto.go |
Emits streaming protobuf RPCs. |
cmd/mobyextgen/parse.go |
Parses and validates stream signatures. |
cmd/mobyextgen/model.go |
Models method kinds and documentation. |
cmd/mobyextgen/descriptor.go |
Marks descriptor streaming directions. |
Files not reviewed (4)
- cmd/mobyextgen/proto.go: Generated file
- cmd/mobyextgen/wire.go: Generated file
- internal/launcher/echo/v1/protogen/wire.gen.go: Generated file
- sdk/sdkapi/protogen/wire.gen.go: Generated file
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Signed-off-by: Paweł Gronowski <git@grono.dev>
Signed-off-by: Paweł Gronowski <git@grono.dev>
There was a problem hiding this comment.
🟡 Changes recommended
Generated streaming adapters return timing-dependent cancellation status codes.
3 open findings
1 resolved since last review
Files not reviewed (4)
- cmd/mobyextgen/proto.go: Generated file
- cmd/mobyextgen/wire.go: Generated file
- internal/launcher/echo/v1/protogen/wire.gen.go: Generated file
- sdk/sdkapi/protogen/wire.gen.go: Generated file
🧠 Review effort: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Streaming lifecycle changes need human validation, and an unresolved signature-validation issue can produce uncompilable adapters.
1 open finding
3 resolved since last review
Files not reviewed (4)
- cmd/mobyextgen/proto.go: Generated file
- cmd/mobyextgen/wire.go: Generated file
- internal/launcher/echo/v1/protogen/wire.gen.go: Generated file
- sdk/sdkapi/protogen/wire.gen.go: Generated file
🧠 Review effort: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
The public streaming API and transport lifecycle need human validation, and runtime tests were not executed.
0 open findings
1 resolved since last review
Files not reviewed (4)
- cmd/mobyextgen/proto.go: Generated file
- cmd/mobyextgen/wire.go: Generated file
- internal/launcher/echo/v1/protogen/wire.gen.go: Generated file
- sdk/sdkapi/protogen/wire.gen.go: Generated file
🧠 Review effort: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Generated-name collisions need correction, and the public streaming lifecycle and cancellation behavior require final human validation.
1 open finding
Files not reviewed (4)
- cmd/mobyextgen/proto.go: Generated file
- cmd/mobyextgen/wire.go: Generated file
- internal/launcher/echo/v1/protogen/wire.gen.go: Generated file
- sdk/sdkapi/protogen/wire.gen.go: Generated file
🧠 Review effort: Balanced
Contract methods with extra or missing parameters were accepted as long as the last parameter was a request pointer, and the generator emitted adapters that did not compile against the contract. The generator now classifies each parameter and accepts only methods with exactly two parameters, the second a request pointer, and an error or (*Reply, error) result. It rejects any other signature with one error that lists the accepted shapes. Signed-off-by: Paweł Gronowski <git@grono.dev>
Contracts can declare server-streaming methods with this shape:
M(ctx, *Req, stream.Sender[T]) error
The generator emits them as protobuf server-streaming RPCs and adapts
the gRPC stream to stream.Sender on both sides.
Stream messages must be local, non-generic structs.
A provider that sends a nil message gets codes.Internal.
A runtime test generates fixture contracts and runs their generated
code over a real gRPC connection and directly in process.
Signed-off-by: Paweł Gronowski <git@grono.dev>
Contracts can declare client-streaming methods with this shape:
M(ctx, stream.Receiver[T]) (*Resp, error)
The generator emits them as protobuf client-streaming RPCs and adapts
the gRPC stream to stream.Receiver on both sides.
The client adapter reads the caller's Receiver synchronously, so a
blocked transport send stops further reads.
A normal io.EOF from the Receiver closes the stream and waits for the
provider's reply.
An io.EOF from Send means the provider finished early, so the adapter
returns the provider's final status.
A nil message from the caller fails with codes.InvalidArgument.
Bidirectional streaming remains rejected.
Signed-off-by: Paweł Gronowski <git@grono.dev>
The generator treated the first method parameter as the context
without checking its type.
It accepted contracts whose first parameter was, for example, a
request pointer or an int.
The generated adapters pass a context.Context, so they did not compile
against such contracts.
The first parameter must now name context.Context.
The check resolves the file's imports, so it accepts a renamed import
of the standard context package and rejects a Context type from any
other package.
Test contracts that declared ctx interface{} now use context.Context.
Signed-off-by: Paweł Gronowski <git@grono.dev>
There was a problem hiding this comment.
🔵 Needs a closer look
Public streaming contracts and transport cancellation semantics need human validation, with runtime tests not executed in this review.
0 open findings
1 resolved since last review
Files not reviewed (4)
- cmd/mobyextgen/proto.go: Generated file
- cmd/mobyextgen/wire.go: Generated file
- internal/launcher/echo/v1/protogen/wire.gen.go: Generated file
- sdk/sdkapi/protogen/wire.gen.go: Generated file
🧠 Review effort: Balanced


Summary
Add typed streaming to extension contracts.
A contract can now declare server-streaming and client-streaming methods with the new
streampackage.mobyextgengenerates protobuf streaming RPCs and adapters for these methods on both sides.Server-streaming shape:
Client-streaming shape:
Bidirectional streams and all other stream signatures are rejected.
Why
stream.Senderandstream.ReceiverWhy not channels
Channels are the Go-native way to pass a sequence of values.
But a stream in a contract can fail, and a channel cannot report that:
Send failures, receive errors, and the final status would each need a second channel or a wrapper type.
A send on a closed channel panics.
Why not iterators
Iterators are closer.
They are synchronous, they can stop early, and they need no goroutines.
But they fit only one direction, and they move the stream outside the method call.
For client streaming, the provider could take
iter.Seq2[*Chunk, error]and range over it.That works, but it still needs rules: an error ends the sequence, the sequence can be ranged only once, and a normal end is not an error.
For server streaming, the provider would have to return
iter.Seq2[*Chunk, error]:The stream then runs after the call, outside the method's context.
It becomes a convention, such as a last
(nil, err)pair in the sequence.If the caller never ranges over it, the stream stays open until the context ends.
A provider could take a
yield func(*Chunk) boolinstead, but aboolcannot say why the other side stopped.That makes it a
Senderwithout an error.SenderandReceiverhave the same shape in both directions and on both sides.The stream stays inside the method call, and the method's return value is the final status.
Code that prefers iterators can still use them:
stream.Allreads aReceiveras aniter.Seq2, andstream.Copysends aniter.Seq2through aSender.Sender and Receiver
What a contract needs is a call that moves one message and returns an error:
The stream belongs to one method call:
SendandRecvreports its own error.Recvreturnsio.EOFwhen the input ends normally.Sendblocks until the receiver or the transport accepts the message.In process, the caller and the provider use these interfaces directly, without an adapter.
Over gRPC, the generated adapters map these methods to gRPC streaming RPCs and convert between the contract structs and the protobuf messages.