Skip to content

feat: add @nexus.varargs, @nexus.unwrap and @nexus.result-field - #332

Closed
moetemp wants to merge 1 commit into
moe/up/resource-factoryfrom
moe/up/varargs-unwrap-result-field
Closed

moetemp wants to merge 1 commit into
moe/up/resource-factoryfrom
moe/up/varargs-unwrap-result-field

Conversation

@moetemp

@moetemp moetemp commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

What changed?

This PR adds @nexus.varargs, @nexus.unwrap and @nexus.result-field, which turn the Stream's publish(records: list<append-record>) -> append-records-response into publish(*values) -> int.

/// @nexus.unwrap
record append-record {
  body: body-t,
}

resource %stream {
  /// @nexus.operation "append-records"
  /// @nexus.varargs name="values"
  /// @nexus.result-field "last-offset"
  publish: func(records: list<append-record>) -> append-records-response;
}
  • @nexus.unwrap goes on a record with one field that isn't omitted. A resource method takes that field's type in place of the record, and wraps each value in the record when it builds the request. A generic record unwraps to its type parameter. The model and the wire keep the record.
  • @nexus.varargs goes on a resource method whose last parameter is a list. The method takes the list's items as positional arguments, and name gives their API name, since name binding uses the WIT name. Python takes *values and makes the other parameters keyword-only, TypeScript takes ...values, .NET takes params T[] values, and Go takes values ...T.
  • @nexus.result-field makes a resource method return one field of the response. An option field of a proto with no presence returns its zero value when the response leaves it unset, so publish returns 0 for an empty publish. A field with presence returns its absence. Go returns the zero value in a future of its own.
  • A resource method can now return a WIT record that its operation returns. Before, only a resource or a proto message could be the result.

nexgen refuses @nexus.unwrap on a record with another number of fields, with @nexus.flatten-in-api, or on a whole operation input. It refuses @nexus.varargs on a method whose last parameter isn't a list, on an operation, or with a name of another parameter. It refuses @nexus.result-field that names no field of the response, on a method whose result isn't a record, or with an output transform on the bound operation.

The stream sample takes all three, so publish is publish(*values: BodyT) -> int in Python, publish(...values: BodyT[]): Promise<Long> in TypeScript and Task<long> PublishAsync(params TBody[] values) in .NET. The writer's write_records takes the values of its records too. A new sample, batch-service, shows all three in the four languages, with a JSON result field that keeps its absence.

It sits on #331. The stack is #308, #310, #311, #297, #296, #303, #304, #309, #318, #319, #320, #321, #322, #323, #324, #325, #326, #327, #328, #329, #330, #331, then this PR.

Why?

The Stream API of the proposal has publish(*values: T) -> int. The WIT keeps the request and the response of the proto, so the three directives map one to the other, and each is general.

The design also unwrapped the parameters of an operation function. This PR unwraps the resource method parameters only, since the facade calls the operation functions with records.

Java has no resource methods, so nothing changes for it.

How did you test it?

A Python test publishes two values and checks the wrapped records and the last offset, and that an empty publish answers 0. A TypeScript test does the same with Long. A .NET compile check publishes two values. Python, TypeScript and Go tests add items to a batch-service batch and check the wrapped items, the field, and the absent field of an empty add (None, undefined, and "" in Go). A .NET compile check calls it. A unit test checks the directives of publish, and rejection fixtures cover each refusal.
cargo validate passes.

A resource method can take the items of its last list as arguments, take a one-field record as its
field, and return one field of the response. The Stream's publish takes values and returns the
last offset.
@moetemp

moetemp commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Moved to moetemp/nexgen#89 for review first. It comes back here after that.

@moetemp moetemp closed this Oct 28, 2026
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.

1 participant