Skip to content

feat(dotnet): add a client target with @nexus.poll - #102

Draft
moetemp wants to merge 40 commits into
moe/up/typescript-client-targetfrom
moe/up/dotnet-client-target
Draft

moetemp wants to merge 40 commits into
moe/up/typescript-client-targetfrom
moe/up/dotnet-client-target

Conversation

@moetemp

@moetemp moetemp commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

What changed?

This PR adds the client target to the .NET generator, with the @nexus.poll iterator.

/// @nexus.poll python="next_read_request" typescript="nextReadRequest"
///   dotnet="StreamPoll.NextReadRequest" items="records" python-resume="resume_read_request"
///   targets="client"
read-records: func(request: read-records-request) -> read-records-response;
  • --targets workflow,client adds a <namespace>.Client namespace with ITemporalClient extension methods per service, client resources and a Get<Resource> factory. A client resource takes the NexusClient of its service.
  • @nexus.poll with a dotnet helper gives <Name>PagesAsync, an IAsyncEnumerable of the pages with a CancellationToken.

Why?

The Python client target has no .NET half.

How did you test it?

Link to a test plan if any -

  • Unit Tests
  • Staging
  • End to End Tests

StreamClientChecks.cs checks a create, an append, a rebuilt handle and the read loop with a fake client. Fixtures cover the .NET name clashes and a result record that holds a resource.

@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from 70a1fbb to 5e2ddc6 Compare November 3, 2026 15:22
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from 841a56f to 1457f2a Compare November 3, 2026 15:22
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from 5e2ddc6 to cf55965 Compare November 4, 2026 09:34
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from 1457f2a to ba06fdf Compare November 4, 2026 09:34
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from cf55965 to ea2b628 Compare November 5, 2026 05:19
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from ba06fdf to 14721c5 Compare November 5, 2026 05:19
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from ea2b628 to a386d04 Compare November 5, 2026 07:27
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from 14721c5 to a121114 Compare November 5, 2026 07:27
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from a386d04 to e314cbb Compare November 5, 2026 22:22
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from a121114 to 7295bd9 Compare November 5, 2026 22:22
moetemp and others added 18 commits November 5, 2026 16:14
A resource with `@nexus.type-parameter "<alias>"` is generic in Python, TypeScript and .NET. An
operation that returns it, or that its method binds, takes a runtime type for each type parameter
of its output, so a response decodes its generic values.
The kept fixtures cover an undeclared type parameter, a generic resource in a record field or in a result with no proto message, and a language that refuses it. The others test directive syntax, rare name clashes or another copy of a kept check.
Co-authored-by: Tim Conley <timothy.conley@temporal.io>
A resource that uses a type parameter, also through its records, is generic in it, as a record
is. So a resource needs no annotation, and `@nexus.type-parameter` is valid only on a type alias
again.
Only a returned resource gives its type parameters, as the guide says, so a
parameter or field that holds one doesn't. The declaration order doesn't change
when a method moves.
A list, map, tuple, result or variant case builds the resource from the record's
message too, so it gets no type argument either.
The old reason said a proto message holds the type arguments, which it doesn't.
Two type parameters such as `body` and `body-t` give the same runtime type name,
and a method can take it too. Either one makes a duplicate parameter or member.
The typed hook of a generic response took only the request, so the terminal
decoded `ReadRecordsResponse[Turn]` with the plain class and lost `Turn`.
C# generics are invariant, so the `<object>` checks refused a `Stream<Turn>` call.
The dispatch now compares generic type definitions and keeps the call's types.
A subclass such as `class TurnStream(Stream[Turn])` has no `__orig_class__`, so
the handle reads `__orig_bases__` too. A factory tests `body_type is None`,
since a class can be falsy.
The TypeScript stream test now decodes a body with a runtime type. The Python
test decodes to the bare handle class, since a generic alias fails for another
reason.
No release has the Python constructor change, so it belongs with the added
entry. The fixed entry now describes the System Nexus dispatch fixes.
A few comments used "name" as a verb, ran over two lines or over 100 columns.
The guide's first paragraph read as one long sentence.
moetemp and others added 16 commits November 5, 2026 19:54
Drop the tests that repeat another test or restate generated code, and keep the
ones that guard behavior. Shorten the guide section and the comments.
Python can't subscript a function, so each generic call is an object whose
subscript binds the types, as in `client.get_stream[Turn](ref=ref)`. A factory's
optional field defaults to `None`.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
`@nexus.close` marks the method that ends a resource. Python calls it at the end of `async with`,
and a call that returns the resource returns a `ResourceCall`.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
`@nexus.varargs` makes a resource method take a list's items as positional arguments, and a list of
a flattened one-field record takes that field's values. The Stream's publish takes values and
returns the last offset.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
Workflow code gets a handle on an existing Stream with `get_stream[T]`, from the fields of its ref,
with no call.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
Each client call and client resource method takes `rpc_metadata` and `rpc_timeout`, and passes
them to the `start_operation` of its Nexus client.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
`@nexus.flatten-in-api` on a request field makes that call take the fields of the field's record,
and the models keep the record. The Stream create takes the id, the owner and the backend of its
ref this way, and the other Stream calls keep `ref`.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
A field can leave the API of one target and take a source in another. A call of a target that omits
a required list sends it empty.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from e314cbb to a983f84 Compare November 6, 2026 08:01
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from 7295bd9 to 34791b9 Compare November 6, 2026 08:01
temporalio/api#895 moves the Producer and the withdrawal fields of a write into one
StreamWriter. The sample WIT also takes the final directives of the open nexgen PRs.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from 34791b9 to 1c68eb9 Compare November 6, 2026 09:08
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from a983f84 to e6111ad Compare November 6, 2026 09:08
moetemp and others added 4 commits November 9, 2026 00:14
…removal

This syncs the stream sample with the owner and withdrawal removal. The WIT and
the stream protos drop the owner, the read-side withdrawal machinery and the
`OWNER_*` close statuses, and add the `StreamClose` read item with the kind-oneof
`Producer`. The Python, TypeScript and .NET bindings and samples are regenerated,
and the hand-written support and tests follow.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
Co-authored-by: Tim Conley <timothy.conley@temporal.io>
The stream sample's TypeScript output gets the client API and the read iterator that Python has. A
client resource takes a service-bound Nexus client, as in Python.
The stream sample's .NET output gets the client API and the read iterator that Python has. A
client resource takes the `NexusClient` of its service, as in Python.
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from e6111ad to 23c26b2 Compare November 9, 2026 07:57
@moetemp
moetemp force-pushed the moe/up/dotnet-client-target branch from 1c68eb9 to e8f8aca Compare November 9, 2026 07:57
@moetemp
moetemp force-pushed the moe/up/typescript-client-target branch from 23c26b2 to a61ab4c Compare November 13, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants