Skip to content

fix: make a list<u8> resource method parameter the proto bytes of its field - #407

Merged
tconley1428 merged 3 commits into
moe/up/stream-writer-sync-1106from
moe/up/bytes-method-param-1112
Nov 12, 2026
Merged

tconley1428 merged 3 commits into
moe/up/stream-writer-sync-1106from
moe/up/bytes-method-param-1112

Conversation

@moetemp

@moetemp moetemp commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

What changed?

This PR makes a list<u8> resource method parameter bytes when it fills a proto bytes field, like that field.

It sits on #374, so the diff shows the stream sample's read taking its from_offset as bytes.

Why?

Without it, read takes list[int], number[] and IReadOnlyList<uint> for a bytes field, so #374 fails the Python type check and the .NET build.

How did you test it?

A Rust unit test and the regenerated stream sample. cargo validate passes.

Please note that a list<u16> or list<u32> parameter of a bytes field also becomes bytes.

A proto-backed record reads that field as `bytes`, so a list of integers could not fill it.
moetemp added a commit that referenced this pull request Nov 12, 2026
# Conflicts:
#	GUIDE.md
#	src/planning/resource_resolution.rs
moetemp added a commit that referenced this pull request Nov 12, 2026
#407 sits on top of the stream sample PR, so its diff shows the change to the sample.
moetemp added a commit that referenced this pull request Nov 12, 2026
The API dropped the read position, the close item and the client epoch. The `read` parameter needs #407 to be `bytes`.
# Conflicts:
#	GUIDE.md
#	src/planning/resource_resolution.rs
The stream `read` takes its `from_offset` as `bytes` in Python, TypeScript and .NET.
@moetemp
moetemp changed the base branch from main to moe/up/stream-writer-sync-1106 November 12, 2026 09:21
moetemp added a commit to temporalio/api that referenced this pull request Nov 12, 2026
That revision makes a list<u8> parameter of a resource method the
bytes of its proto field, which the read's from-offset needs.
@moetemp
moetemp marked this pull request as ready for review November 12, 2026 17:55
@moetemp
moetemp requested a review from a team as a code owner November 12, 2026 17:55
@tconley1428
tconley1428 merged commit 1d6037f into moe/up/stream-writer-sync-1106 Nov 12, 2026
5 checks passed
@tconley1428
tconley1428 deleted the moe/up/bytes-method-param-1112 branch November 12, 2026 18:09
moetemp added a commit that referenced this pull request Nov 13, 2026
…#895 (#374)

* fix(typescript): infer Long from protobuf resource bindings

* feat: let a resource declare its type parameters

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.

* test: drop the low-value type parameter rejection fixtures

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.

* docs: shorten the generic resources section of the guide

* test: assert the full message of each type parameter rejection

* docs: say why a generic handle keeps its runtime types

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* feat: infer the type parameters of a resource

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.

* fix(python): decode generic resources from their type arguments (#367)

* fix: infer resource type parameters from results in declaration order

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.

* fix: refuse a generic resource anywhere in a record field

A list, map, tuple, result or variant case builds the resource from the record's
message too, so it gets no type argument either.

* fix: say that a direct generic resource result isn't supported yet

The old reason said a proto message holds the type arguments, which it doesn't.

* fix: refuse runtime type names that collide with each other or a method

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.

* fix(python): keep the runtime output type in the System Nexus dispatch

The typed hook of a generic response took only the request, so the terminal
decoded `ReadRecordsResponse[Turn]` with the plain class and lost `Turn`.

* fix(dotnet): send generic System Nexus operations to the generic hook

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.

* fix(python): read the type argument of a subclassed generic handle

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.

* test: cover a typed TypeScript read and a bare Python handle class

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.

* chore: fold the generic resource changelog and describe the fixes

No release has the Python constructor change, so it belongs with the added
entry. The fixed entry now describes the System Nexus dispatch fixes.

* docs: reword the generic resource comments and wrap long lines

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.

* chore: fold the generic resource changelog into one entry

* chore: trim the generic resource tests and docs

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.

* feat(python): take a resource's runtime types as a subscript

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>

* feat: close a resource at the end of async with

`@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>

* feat: add @nexus.varargs and flatten a list of one-field records

`@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>

* feat(python): add a Workflow resource factory

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>

* feat(python): add RPC options to client calls

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>

* feat: flatten the record of one request field

`@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>

* feat: limit `@nexus.api-omit` to some targets

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>

* chore(samples): sync the stream sample with StreamWriter

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>

* chore(samples): sync the stream sample with the owner and withdrawal 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>

* chore(samples): format the stream test with oxfmt

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Omitted an operation's public function with `@nexus.api-omit`.

A top-level `func` with `@nexus.api-omit` keeps the operation registered and bound to its resource,
but drops the public top-level function and its export. A caller reaches the operation through the
resource. On a function the directive omits the workflow top-level function, so its `targets` must
include `workflow`.

The stream sample omits four operations, so `append-records`, `close-stream`, `read-records` and
`describe-stream` have no public function and the `Stream` resource is the way in. `create-stream`
stays public, since the Workflow has no other way to create a Stream.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Made a `list<u8>` case of a proto oneof the proto `bytes`.

A variant with `@nexus.proto` reads an unnamed `list<u8>` case as the proto `bytes`, the same rule
as a field of a proto-backed record. Another width still fails against a `bytes` member.

The stream sample's read position is a oneof of a `bytes` offset and a start enum, which showed two
more gaps. An enum case kept its proto type, so its WIT enum was pruned. It is now planned as the
bound enum, like a record case. A resource method parameter named after the oneof was dropped,
since the request plan walked the oneof's members. It now binds to the record's oneof field.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the latest temporalio/api#895.

The WIT and protos are those of temporalio/api#895 at b0b2657e. The WIT adds the `@nexus.api-omit`
of #405 and the `@nexus.generic` of #399, which the API WIT doesn't have yet. A directive that
nexgen doesn't support yet stays a TODO comment, and an offset is an inline `list<u8>`, since nexgen
reads only that as `bytes`.

* Synced the stream sample with the removal of `create_wait`.

temporalio/api#895 at 0ac6b717 drops `create_wait` from a read and renumbers the later fields, so
the WIT, the protos and their bindings follow it.

* Synced the stream sample with the Stream writes and items reshape.

temporalio/api#895 at 23fac086 drops `StreamWriter` for a plain `producer`, moves the offset into
the read items, and makes the required fields required. The WIT is the API's as is, and the
support helpers are the `producer` sources that it names.

* Synced the stream sample with the close offset and namespace comments.

* Synced the stream sample with the one fence rule for every Producer.

* Synced the stream sample with the unknown writer removal.

* Synced the stream sample with the Workflow Producer comments.

The API comments now say when a Workflow Producer writes and when it only closes.

* Synced the stream sample with the read and Record reshape.

The API dropped the read position, the close item and the client epoch. The `read` parameter needs #407 to be `bytes`.

* fix: make a `list<u8>` resource method parameter the proto `bytes` of its field (#407)

* Typed a `list<u8>` method parameter of a proto `bytes` field as bytes.

A proto-backed record reads that field as `bytes`, so a list of integers could not fill it.

* Regenerated the stream sample with the bytes parameter.

The stream `read` takes its `from_offset` as `bytes` in Python, TypeScript and .NET.

* Synced the stream sample with the Store rename and the append result.

The API renamed `backend` to `store` in the Stream ref and dropped the `append` result transform.
So `append` returns the `AppendRecordsResponse` in every target. The two optional `producer`
parameters of the API's WIT are not in this sync, since they need a generator change.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the sequence parameter of append.

The API gave `append` an optional `sequence`, so a caller can number a batch and the Store can
dedupe a resent one. The optional `producer` parameter still waits for the generator change.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the offset and Workflow Producer comments.

The API now says that two offsets of one Stream compare bytewise. It also says that Workflow code
on a Store writes through a Local Activity. Only comments changed, so the samples only move in docs.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Told why the stream sample offset comment matters.

---------

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
tconley1428 added a commit that referenced this pull request Nov 13, 2026
…parameter in Python (#408)

* fix(typescript): infer Long from protobuf resource bindings

* feat: let a resource declare its type parameters

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.

* test: drop the low-value type parameter rejection fixtures

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.

* docs: shorten the generic resources section of the guide

* test: assert the full message of each type parameter rejection

* docs: say why a generic handle keeps its runtime types

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* feat: infer the type parameters of a resource

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.

* fix(python): decode generic resources from their type arguments (#367)

* fix: infer resource type parameters from results in declaration order

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.

* fix: refuse a generic resource anywhere in a record field

A list, map, tuple, result or variant case builds the resource from the record's
message too, so it gets no type argument either.

* fix: say that a direct generic resource result isn't supported yet

The old reason said a proto message holds the type arguments, which it doesn't.

* fix: refuse runtime type names that collide with each other or a method

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.

* fix(python): keep the runtime output type in the System Nexus dispatch

The typed hook of a generic response took only the request, so the terminal
decoded `ReadRecordsResponse[Turn]` with the plain class and lost `Turn`.

* fix(dotnet): send generic System Nexus operations to the generic hook

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.

* fix(python): read the type argument of a subclassed generic handle

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.

* test: cover a typed TypeScript read and a bare Python handle class

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.

* chore: fold the generic resource changelog and describe the fixes

No release has the Python constructor change, so it belongs with the added
entry. The fixed entry now describes the System Nexus dispatch fixes.

* docs: reword the generic resource comments and wrap long lines

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.

* chore: fold the generic resource changelog into one entry

* chore: trim the generic resource tests and docs

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.

* feat(python): take a resource's runtime types as a subscript

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>

* feat: close a resource at the end of async with

`@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>

* feat: add @nexus.varargs and flatten a list of one-field records

`@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>

* feat(python): add a Workflow resource factory

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>

* feat(python): add RPC options to client calls

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>

* feat: flatten the record of one request field

`@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>

* feat: limit `@nexus.api-omit` to some targets

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>

* chore(samples): sync the stream sample with StreamWriter

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>

* chore(samples): sync the stream sample with the owner and withdrawal 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>

* chore(samples): format the stream test with oxfmt

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Omitted an operation's public function with `@nexus.api-omit`.

A top-level `func` with `@nexus.api-omit` keeps the operation registered and bound to its resource,
but drops the public top-level function and its export. A caller reaches the operation through the
resource. On a function the directive omits the workflow top-level function, so its `targets` must
include `workflow`.

The stream sample omits four operations, so `append-records`, `close-stream`, `read-records` and
`describe-stream` have no public function and the `Stream` resource is the way in. `create-stream`
stays public, since the Workflow has no other way to create a Stream.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Made a `list<u8>` case of a proto oneof the proto `bytes`.

A variant with `@nexus.proto` reads an unnamed `list<u8>` case as the proto `bytes`, the same rule
as a field of a proto-backed record. Another width still fails against a `bytes` member.

The stream sample's read position is a oneof of a `bytes` offset and a start enum, which showed two
more gaps. An enum case kept its proto type, so its WIT enum was pruned. It is now planned as the
bound enum, like a record case. A resource method parameter named after the oneof was dropped,
since the request plan walked the oneof's members. It now binds to the record's oneof field.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the latest temporalio/api#895.

The WIT and protos are those of temporalio/api#895 at b0b2657e. The WIT adds the `@nexus.api-omit`
of #405 and the `@nexus.generic` of #399, which the API WIT doesn't have yet. A directive that
nexgen doesn't support yet stays a TODO comment, and an offset is an inline `list<u8>`, since nexgen
reads only that as `bytes`.

* Synced the stream sample with the removal of `create_wait`.

temporalio/api#895 at 0ac6b717 drops `create_wait` from a read and renumbers the later fields, so
the WIT, the protos and their bindings follow it.

* Synced the stream sample with the Stream writes and items reshape.

temporalio/api#895 at 23fac086 drops `StreamWriter` for a plain `producer`, moves the offset into
the read items, and makes the required fields required. The WIT is the API's as is, and the
support helpers are the `producer` sources that it names.

* Synced the stream sample with the close offset and namespace comments.

* Synced the stream sample with the one fence rule for every Producer.

* Synced the stream sample with the unknown writer removal.

* Synced the stream sample with the Workflow Producer comments.

The API comments now say when a Workflow Producer writes and when it only closes.

* Synced the stream sample with the read and Record reshape.

The API dropped the read position, the close item and the client epoch. The `read` parameter needs #407 to be `bytes`.

* fix: make a `list<u8>` resource method parameter the proto `bytes` of its field (#407)

* Typed a `list<u8>` method parameter of a proto `bytes` field as bytes.

A proto-backed record reads that field as `bytes`, so a list of integers could not fill it.

* Regenerated the stream sample with the bytes parameter.

The stream `read` takes its `from_offset` as `bytes` in Python, TypeScript and .NET.

* Filled an unset optional parameter from its field's source in Python.

The generated code passed an optional parameter as it was and dropped the source of the field. A
caller that passed nothing then sent no value for a required field.

* Filled an unset optional parameter from its source in TypeScript.

A resource method passed an optional parameter as it was and dropped the source of the field. A
caller that passed nothing then sent no value for a required field.

* Filled an unset optional parameter from its source in .NET.

A resource method passed an optional parameter as it was and dropped the source of the field. The
complete request also took a flattened list parameter as it was, so it did not compile.

* Gave the stream sample's append and close an optional producer.

The sample shows the source fallback in its generated Python, TypeScript and .NET. Its client source
helper is `default_producer`, since it picks the Producer by where the code runs.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the Store rename and the append result.

The API renamed `backend` to `store` in the Stream ref and dropped the `append` result transform.
So `append` returns the `AppendRecordsResponse` in every target. The two optional `producer`
parameters of the API's WIT are not in this sync, since they need a generator change.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Said in the sample's Producer docs that a call can pass its own.

The docs of both `producer` fields match the API's WIT, which has the two optional parameters.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the sequence parameter of append.

The API gave `append` an optional `sequence`, so a caller can number a batch and the Store can
dedupe a resent one. The optional `producer` parameter still waits for the generator change.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the offset and Workflow Producer comments.

The API now says that two offsets of one Stream compare bytewise. It also says that Workflow code
on a Store writes through a Local Activity. Only comments changed, so the samples only move in docs.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Imported temporalio.client only for type checking in Python clients. (#409)

The SDK's `Client` inherits the generated extension, so `temporalio.client` imports the generated
package. A run-time import back made importing the package first a circular import.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Filled an unset optional parameter from its field's default in Python.

A resource method passed an optional parameter as it was and dropped the default of the field.
A source is a helper value and never an argument, so it no longer fills an optional parameter.

* Took the source fallback out of TypeScript resource methods.

A resource method hands an optional parameter to its operation, which applies the field's default.
A source is a helper value and never an argument, so it no longer fills an optional parameter.

* Took the source fallback out of .NET resource methods.

The stream sample's resource methods go through the options type, which applies the default.
A source is a helper value and never an argument, so it no longer fills an optional parameter.

* Gave the stream sample's Producer a default instead of a source.

A default is an omittable argument, which the optional `producer` of `append` and `close` needs.
The guide and the changelog describe the default rule for resource methods.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Told why the stream sample offset comment matters.

---------

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
tconley1428 added a commit that referenced this pull request Nov 13, 2026
#410)

* fix(typescript): infer Long from protobuf resource bindings

* feat: let a resource declare its type parameters

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.

* test: drop the low-value type parameter rejection fixtures

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.

* docs: shorten the generic resources section of the guide

* test: assert the full message of each type parameter rejection

* docs: say why a generic handle keeps its runtime types

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* feat: infer the type parameters of a resource

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.

* fix(python): decode generic resources from their type arguments (#367)

* fix: infer resource type parameters from results in declaration order

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.

* fix: refuse a generic resource anywhere in a record field

A list, map, tuple, result or variant case builds the resource from the record's
message too, so it gets no type argument either.

* fix: say that a direct generic resource result isn't supported yet

The old reason said a proto message holds the type arguments, which it doesn't.

* fix: refuse runtime type names that collide with each other or a method

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.

* fix(python): keep the runtime output type in the System Nexus dispatch

The typed hook of a generic response took only the request, so the terminal
decoded `ReadRecordsResponse[Turn]` with the plain class and lost `Turn`.

* fix(dotnet): send generic System Nexus operations to the generic hook

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.

* fix(python): read the type argument of a subclassed generic handle

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.

* test: cover a typed TypeScript read and a bare Python handle class

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.

* chore: fold the generic resource changelog and describe the fixes

No release has the Python constructor change, so it belongs with the added
entry. The fixed entry now describes the System Nexus dispatch fixes.

* docs: reword the generic resource comments and wrap long lines

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.

* chore: fold the generic resource changelog into one entry

* chore: trim the generic resource tests and docs

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.

* feat(python): take a resource's runtime types as a subscript

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>

* feat: close a resource at the end of async with

`@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>

* feat: add @nexus.varargs and flatten a list of one-field records

`@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>

* feat(python): add a Workflow resource factory

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>

* feat(python): add RPC options to client calls

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>

* feat: flatten the record of one request field

`@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>

* feat: limit `@nexus.api-omit` to some targets

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>

* chore(samples): sync the stream sample with StreamWriter

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>

* chore(samples): sync the stream sample with the owner and withdrawal 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>

* chore(samples): format the stream test with oxfmt

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Omitted an operation's public function with `@nexus.api-omit`.

A top-level `func` with `@nexus.api-omit` keeps the operation registered and bound to its resource,
but drops the public top-level function and its export. A caller reaches the operation through the
resource. On a function the directive omits the workflow top-level function, so its `targets` must
include `workflow`.

The stream sample omits four operations, so `append-records`, `close-stream`, `read-records` and
`describe-stream` have no public function and the `Stream` resource is the way in. `create-stream`
stays public, since the Workflow has no other way to create a Stream.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Made a `list<u8>` case of a proto oneof the proto `bytes`.

A variant with `@nexus.proto` reads an unnamed `list<u8>` case as the proto `bytes`, the same rule
as a field of a proto-backed record. Another width still fails against a `bytes` member.

The stream sample's read position is a oneof of a `bytes` offset and a start enum, which showed two
more gaps. An enum case kept its proto type, so its WIT enum was pruned. It is now planned as the
bound enum, like a record case. A resource method parameter named after the oneof was dropped,
since the request plan walked the oneof's members. It now binds to the record's oneof field.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the latest temporalio/api#895.

The WIT and protos are those of temporalio/api#895 at b0b2657e. The WIT adds the `@nexus.api-omit`
of #405 and the `@nexus.generic` of #399, which the API WIT doesn't have yet. A directive that
nexgen doesn't support yet stays a TODO comment, and an offset is an inline `list<u8>`, since nexgen
reads only that as `bytes`.

* Synced the stream sample with the removal of `create_wait`.

temporalio/api#895 at 0ac6b717 drops `create_wait` from a read and renumbers the later fields, so
the WIT, the protos and their bindings follow it.

* Synced the stream sample with the Stream writes and items reshape.

temporalio/api#895 at 23fac086 drops `StreamWriter` for a plain `producer`, moves the offset into
the read items, and makes the required fields required. The WIT is the API's as is, and the
support helpers are the `producer` sources that it names.

* Synced the stream sample with the close offset and namespace comments.

* Synced the stream sample with the one fence rule for every Producer.

* Synced the stream sample with the unknown writer removal.

* Synced the stream sample with the Workflow Producer comments.

The API comments now say when a Workflow Producer writes and when it only closes.

* Synced the stream sample with the read and Record reshape.

The API dropped the read position, the close item and the client epoch. The `read` parameter needs #407 to be `bytes`.

* fix: make a `list<u8>` resource method parameter the proto `bytes` of its field (#407)

* Typed a `list<u8>` method parameter of a proto `bytes` field as bytes.

A proto-backed record reads that field as `bytes`, so a list of integers could not fill it.

* Regenerated the stream sample with the bytes parameter.

The stream `read` takes its `from_offset` as `bytes` in Python, TypeScript and .NET.

* Filled an unset optional parameter from its field's source in Python.

The generated code passed an optional parameter as it was and dropped the source of the field. A
caller that passed nothing then sent no value for a required field.

* Filled an unset optional parameter from its source in TypeScript.

A resource method passed an optional parameter as it was and dropped the source of the field. A
caller that passed nothing then sent no value for a required field.

* Filled an unset optional parameter from its source in .NET.

A resource method passed an optional parameter as it was and dropped the source of the field. The
complete request also took a flattened list parameter as it was, so it did not compile.

* Gave the stream sample's append and close an optional producer.

The sample shows the source fallback in its generated Python, TypeScript and .NET. Its client source
helper is `default_producer`, since it picks the Producer by where the code runs.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Imported temporalio.client only for type checking in Python clients.

The SDK's `Client` inherits the generated extension, so `temporalio.client` imports the generated
package. A run-time import back made importing the package first a circular import.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the Store rename and the append result.

The API renamed `backend` to `store` in the Stream ref and dropped the `append` result transform.
So `append` returns the `AppendRecordsResponse` in every target. The two optional `producer`
parameters of the API's WIT are not in this sync, since they need a generator change.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Said in the sample's Producer docs that a call can pass its own.

The docs of both `producer` fields match the API's WIT, which has the two optional parameters.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the sequence parameter of append.

The API gave `append` an optional `sequence`, so a caller can number a batch and the Store can
dedupe a resent one. The optional `producer` parameter still waits for the generator change.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Synced the stream sample with the offset and Workflow Producer comments.

The API now says that two offsets of one Stream compare bytewise. It also says that Workflow code
on a Store writes through a Local Activity. Only comments changed, so the samples only move in docs.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Left a resource method out of one Python target with `@nexus.api-omit`.

A resource method could not be left out of one target, and the stream sample needs a Workflow
`Stream` with no `append` and no `close`. Without its close method, a target's resource is not a
context manager, and a method that no target keeps is refused.

* Left an api-omitted resource method out of the TypeScript Workflow API.

A TypeScript resource method calls the Workflow API, so a method omitted for `workflow` goes from
the class. Its request helper goes too when no other method used it.

* Left an api-omitted resource method out of the .NET Workflow API.

A .NET resource method calls the Workflow API, so a method omitted for `workflow` goes from the
class. The request overload that only it called becomes private.

* Refused a resource method omitted for the Workflow target in Go.

Go has no sample that would show its output without the method, so it refuses the directive as it
refuses `@nexus.varargs`. A method omitted only for `client` changes nothing in Go.

* Left `append` and `close` out of the stream sample's Workflow `Stream`.

In this release Workflow code can't append to a Stream or close it, so the Workflow target omits
both methods. The client `Stream` keeps them, and the Workflow `create_stream` is a plain call.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Imported temporalio.client only for type checking in Python clients. (#409)

The SDK's `Client` inherits the generated extension, so `temporalio.client` imports the generated
package. A run-time import back made importing the package first a circular import.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Filled an unset optional parameter from its field's default in Python.

A resource method passed an optional parameter as it was and dropped the default of the field.
A source is a helper value and never an argument, so it no longer fills an optional parameter.

* Took the source fallback out of TypeScript resource methods.

A resource method hands an optional parameter to its operation, which applies the field's default.
A source is a helper value and never an argument, so it no longer fills an optional parameter.

* Took the source fallback out of .NET resource methods.

The stream sample's resource methods go through the options type, which applies the default.
A source is a helper value and never an argument, so it no longer fills an optional parameter.

* Gave the stream sample's Producer a default instead of a source.

A default is an omittable argument, which the optional `producer` of `append` and `close` needs.
The guide and the changelog describe the default rule for resource methods.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Told why the stream sample offset comment matters.

* Rewrote the architecture note on omitted resource methods.

The note listed type names and did not say what happens. It says what the directive does and
that planning owns the choice.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>

* Cut the architecture note on omitted resource methods to its gist.

---------

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
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