diff --git a/advanced/samples/dotnet/wit/user-service/Models.cs b/advanced/samples/dotnet/wit/user-service/Models.cs
index bee02cbf5..2cd81a702 100644
--- a/advanced/samples/dotnet/wit/user-service/Models.cs
+++ b/advanced/samples/dotnet/wit/user-service/Models.cs
@@ -9,6 +9,20 @@
namespace Nexgen.UserService
{
+ [GeneratedCode("nexgen", null)]
+ public record GetProfileRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GetProfileRequest(string userId)
+ {
+ UserId = userId;
+ }
+
+ public string UserId { get; init; }
+ }
+
[GeneratedCode("nexgen", null)]
public record GetUserRequest
{
@@ -58,4 +72,18 @@ public UpdateEmailRequest(string userId, string email)
public string Email { get; init; }
}
+ [GeneratedCode("nexgen", null)]
+ public record UserProfile
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UserProfile(string displayName)
+ {
+ DisplayName = displayName;
+ }
+
+ public string DisplayName { get; init; }
+ }
+
}
diff --git a/advanced/samples/dotnet/wit/user-service/Resources.cs b/advanced/samples/dotnet/wit/user-service/Resources.cs
index ecd904a4b..562d4a77f 100644
--- a/advanced/samples/dotnet/wit/user-service/Resources.cs
+++ b/advanced/samples/dotnet/wit/user-service/Resources.cs
@@ -52,6 +52,14 @@ public async Task NotifyAsync(string? reason, IReadOnlyList channels, st
await RequireNexgenClient().ExecuteNexusOperationAsync(svc => svc.Notify(request)).ConfigureAwait(true);
}
+ [GeneratedCode("nexgen", null)]
+ public async Task GetProfileAsync()
+ {
+ var request = new GetProfileRequest(UserId);
+ var result = await RequireNexgenClient().ExecuteNexusOperationAsync(svc => svc.GetProfile(request)).ConfigureAwait(true);
+ return result;
+ }
+
}
}
diff --git a/advanced/samples/dotnet/wit/user-service/Services.cs b/advanced/samples/dotnet/wit/user-service/Services.cs
index 231cffe25..6ad0537a0 100644
--- a/advanced/samples/dotnet/wit/user-service/Services.cs
+++ b/advanced/samples/dotnet/wit/user-service/Services.cs
@@ -29,6 +29,10 @@ internal interface IUserService
[NexusOperation("Notify")]
void Notify(NotifyRequest request);
+ [GeneratedCode("nexgen", null)]
+ [NexusOperation("GetProfile")]
+ UserProfile GetProfile(GetProfileRequest request);
+
}
[GeneratedCode("nexgen", null)]
@@ -80,6 +84,20 @@ public NotifyOptions(string userId, IReadOnlyList channels, string sende
public string Sender { get; set; }
}
+ [GeneratedCode("nexgen", null)]
+ public class GetProfileOptions
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GetProfileOptions(string userId)
+ {
+ UserId = userId;
+ }
+
+ public string UserId { get; set; }
+ }
+
[GeneratedCode("nexgen", null)]
public class UserServiceClient
{
@@ -137,6 +155,21 @@ public Task NotifyAsync(NotifyOptions options)
return NotifyAsync(request);
}
+ [GeneratedCode("nexgen", null)]
+ private async Task GetProfileAsync(GetProfileRequest request)
+ {
+ var result = await _client.ExecuteNexusOperationAsync(svc => svc.GetProfile(request)).ConfigureAwait(true);
+ return result;
+ }
+
+ /// Options for the operation.
+ [GeneratedCode("nexgen", null)]
+ public Task GetProfileAsync(GetProfileOptions options)
+ {
+ var request = new GetProfileRequest(options.UserId);
+ return GetProfileAsync(request);
+ }
+
}
}
diff --git a/advanced/samples/go/userservice/userservice.go b/advanced/samples/go/userservice/userservice.go
index 16d297683..dfa3e0c63 100644
--- a/advanced/samples/go/userservice/userservice.go
+++ b/advanced/samples/go/userservice/userservice.go
@@ -52,6 +52,14 @@ func (m notifyRequest) GetSender() string {
return m.Sender
}
+type getProfileRequest struct {
+ UserID string
+}
+
+func (m getProfileRequest) GetUserID() string {
+ return m.UserID
+}
+
// --- Resources ---
type User struct {
@@ -80,6 +88,10 @@ func (u *User) Notify(ctx workflow.Context, reason string, channels []string, se
return notify(ctx, notifyRequest{UserID: u.UserID, Reason: reasonPtr, Channels: channels, Sender: sender})
}
+func (u *User) GetProfile(ctx workflow.Context) workflow.Future {
+ return getProfile(ctx, getProfileRequest{UserID: u.UserID})
+}
+
// --- Operations (internal) ---
func getUser(ctx workflow.Context, request getUserRequest) workflow.Future {
@@ -100,8 +112,23 @@ func notify(ctx workflow.Context, request notifyRequest) workflow.Future {
return fut
}
+func getProfile(ctx workflow.Context, request getProfileRequest) workflow.Future {
+ c := workflow.NewNexusClient("user-service", "UserService")
+ fut := c.ExecuteOperation(ctx, "GetProfile", request, workflow.NexusOperationOptions{})
+ return fut
+}
+
// --- Operations (public API) ---
+type UserProfile struct {
+ // DisplayName - Required.
+ DisplayName string
+}
+
+func (m UserProfile) GetDisplayName() string {
+ return m.DisplayName
+}
+
type GetUserOptions struct {
// UserID - Required.
UserID string
@@ -150,3 +177,14 @@ func Notify(ctx workflow.Context, opts NotifyOptions) workflow.Future {
Sender: opts.Sender,
})
}
+
+type GetProfileOptions struct {
+ // UserID - Required.
+ UserID string
+}
+
+func GetProfile(ctx workflow.Context, opts GetProfileOptions) workflow.Future {
+ return getProfile(ctx, getProfileRequest{
+ UserID: opts.UserID,
+ })
+}
diff --git a/advanced/samples/inputs/user-service.wit b/advanced/samples/inputs/user-service.wit
index d3ee118b1..9c4da7d00 100644
--- a/advanced/samples/inputs/user-service.wit
+++ b/advanced/samples/inputs/user-service.wit
@@ -10,6 +10,7 @@ interface user-service {
update-email: func(email: string) -> user-result;
notify: func(reason: option, channels: list, sender: string);
+ get-profile: func() -> user-profile;
}
type user-result = own;
@@ -30,7 +31,16 @@ interface user-service {
sender: string,
}
+ record get-profile-request {
+ user-id: string,
+ }
+
+ record user-profile {
+ display-name: string,
+ }
+
get-user: func(request: get-user-request) -> user-result;
update-email: func(request: update-email-request) -> user-result;
notify: func(request: notify-request);
+ get-profile: func(request: get-profile-request) -> user-profile;
}
diff --git a/advanced/samples/python/tests/test_user_service.py b/advanced/samples/python/tests/test_user_service.py
index e4f11e0aa..1b57bc04f 100644
--- a/advanced/samples/python/tests/test_user_service.py
+++ b/advanced/samples/python/tests/test_user_service.py
@@ -71,6 +71,15 @@ async def notify(
) -> None:
self.calls.append(("Notify", input))
+ @sync_operation
+ async def get_profile(
+ self,
+ _ctx: StartOperationContext,
+ input: user_service_models.GetProfileRequest,
+ ) -> user_service_models.UserProfile:
+ self.calls.append(("GetProfile", input))
+ return user_service_models.UserProfile(display_name="Alice")
+
@workflow.defn
class UserServiceCallerWorkflow:
@@ -79,6 +88,7 @@ async def run(self) -> User:
service = user_service.UserServiceClient("user-service")
user = await service.get_user(user_id="user-123")
await user.notify(sender="ops")
+ assert (await user.get_profile()).display_name == "Alice"
return await user.update_email("new@example.com")
@@ -126,7 +136,7 @@ async def test_get_user_returns_user_resource(env: WorkflowEnvironment) -> None:
assert user.user_id == "user-123"
assert user.email == "new@example.com"
- assert len(service_handler.calls) == 3
+ assert len(service_handler.calls) == 4
get_user_operation, get_user_request = service_handler.calls[0]
assert get_user_operation == "GetUser"
assert isinstance(get_user_request, user_service_models.GetUserRequest)
@@ -138,7 +148,12 @@ async def test_get_user_returns_user_resource(env: WorkflowEnvironment) -> None:
user_id="user-123", reason=None, channels=[], sender="ops"
)
- update_operation, update_request = service_handler.calls[2]
+ assert service_handler.calls[2] == (
+ "GetProfile",
+ user_service_models.GetProfileRequest(user_id="user-123"),
+ )
+
+ update_operation, update_request = service_handler.calls[3]
assert update_operation == "UpdateEmail"
assert isinstance(update_request, user_service_models.UpdateEmailRequest)
assert update_request.user_id == "user-123"
diff --git a/advanced/samples/python/wit/user_service/__init__.py b/advanced/samples/python/wit/user_service/__init__.py
index e3b7b5332..52316ad55 100644
--- a/advanced/samples/python/wit/user_service/__init__.py
+++ b/advanced/samples/python/wit/user_service/__init__.py
@@ -58,4 +58,10 @@ def __init__(
): _NexusOperationInfo(
operation=_services.UserService.notify,
),
+ (
+ "UserService",
+ "GetProfile",
+ ): _NexusOperationInfo(
+ operation=_services.UserService.get_profile,
+ ),
}
diff --git a/advanced/samples/python/wit/user_service/_resources/user.py b/advanced/samples/python/wit/user_service/_resources/user.py
index 9fc4c2a69..4ca57a541 100644
--- a/advanced/samples/python/wit/user_service/_resources/user.py
+++ b/advanced/samples/python/wit/user_service/_resources/user.py
@@ -6,8 +6,10 @@
import typing
from ..models import (
+ GetProfileRequest,
NotifyRequest,
UpdateEmailRequest,
+ UserProfile,
)
@@ -56,6 +58,20 @@ async def notify(
)
await handle
+ async def get_profile(
+ self,
+ ) -> UserProfile:
+ request = GetProfileRequest(
+ user_id=self.user_id,
+ )
+ nexus_client = getattr(self, "_nexus_client")
+ handle = await nexus_client.start_operation(
+ operation="GetProfile",
+ input=request,
+ output_type=UserProfile,
+ )
+ return await handle
+
def bind_user_client(
resource: User,
diff --git a/advanced/samples/python/wit/user_service/models.py b/advanced/samples/python/wit/user_service/models.py
index 1e24e4072..dd2003c56 100644
--- a/advanced/samples/python/wit/user_service/models.py
+++ b/advanced/samples/python/wit/user_service/models.py
@@ -22,3 +22,13 @@ class NotifyRequest:
reason: str | None = None
channels: list[str] | None = dataclasses.field(default_factory=list)
sender: str
+
+
+@dataclasses.dataclass(slots=True)
+class GetProfileRequest:
+ user_id: str
+
+
+@dataclasses.dataclass(slots=True)
+class UserProfile:
+ display_name: str
diff --git a/advanced/samples/python/wit/user_service/services.py b/advanced/samples/python/wit/user_service/services.py
index 9d3706b55..5427576a4 100644
--- a/advanced/samples/python/wit/user_service/services.py
+++ b/advanced/samples/python/wit/user_service/services.py
@@ -7,9 +7,11 @@
import temporalio.workflow
from .models import (
+ GetProfileRequest,
GetUserRequest,
NotifyRequest,
UpdateEmailRequest,
+ UserProfile,
)
from ._resources.user import (
User,
@@ -37,6 +39,11 @@ class UserService:
None,
] = Operation(name="Notify")
+ get_profile: Operation[
+ GetProfileRequest,
+ UserProfile,
+ ] = Operation(name="GetProfile")
+
class UserServiceClient:
def __init__(self, endpoint: str) -> None:
@@ -97,3 +104,17 @@ async def notify(
operation="Notify",
input=request,
)
+
+ async def get_profile(
+ self,
+ *,
+ user_id: str,
+ ) -> temporalio.workflow.NexusOperationHandle[UserProfile]:
+ request = GetProfileRequest(
+ user_id=user_id,
+ )
+ return await self._nexus_client.start_operation(
+ operation="GetProfile",
+ input=request,
+ output_type=UserProfile,
+ )
diff --git a/advanced/samples/typescript/tests/user-service.test.ts b/advanced/samples/typescript/tests/user-service.test.ts
index 7e7068c60..72af389a6 100644
--- a/advanced/samples/typescript/tests/user-service.test.ts
+++ b/advanced/samples/typescript/tests/user-service.test.ts
@@ -30,6 +30,9 @@ describe("user-service generated output", () => {
return new User(input.userId, input.email);
},
async notify() {},
+ async getProfile() {
+ return { displayName: "Alice" };
+ },
});
const result = await executeWorkflowWithNexus<{
diff --git a/advanced/samples/typescript/wit/user-service/index.ts b/advanced/samples/typescript/wit/user-service/index.ts
index 2027ac46d..7fca3cadc 100644
--- a/advanced/samples/typescript/wit/user-service/index.ts
+++ b/advanced/samples/typescript/wit/user-service/index.ts
@@ -1,4 +1,10 @@
// Generated by nexgen latest. DO NOT EDIT!
export { userService, UserServiceClient } from "./services";
-export type { GetUserRequest, NotifyRequest, UpdateEmailRequest } from "./models";
+export type {
+ GetProfileRequest,
+ GetUserRequest,
+ NotifyRequest,
+ UpdateEmailRequest,
+ UserProfile,
+} from "./models";
diff --git a/advanced/samples/typescript/wit/user-service/models.ts b/advanced/samples/typescript/wit/user-service/models.ts
index d3467197f..9d6ebab71 100644
--- a/advanced/samples/typescript/wit/user-service/models.ts
+++ b/advanced/samples/typescript/wit/user-service/models.ts
@@ -15,3 +15,11 @@ export interface NotifyRequest {
channels?: string[];
sender: string;
}
+
+export interface UserProfile {
+ displayName: string;
+}
+
+export interface GetProfileRequest {
+ userId: string;
+}
diff --git a/advanced/samples/typescript/wit/user-service/resources.ts b/advanced/samples/typescript/wit/user-service/resources.ts
index 5053e0855..94df9060d 100644
--- a/advanced/samples/typescript/wit/user-service/resources.ts
+++ b/advanced/samples/typescript/wit/user-service/resources.ts
@@ -1,6 +1,7 @@
// Generated by nexgen latest. DO NOT EDIT!
import * as workflow from "@temporalio/workflow";
+import type { UserProfile } from "./models";
import { userService } from "./services";
const userClient = Symbol("nexgen.User.client");
@@ -40,6 +41,15 @@ export class User {
);
await handle.result();
}
+
+ public async getProfile(): Promise {
+ const request = { userId: this.userId };
+ const handle = await requireUserClient(this).startOperation(
+ userService.operations.getProfile,
+ request,
+ );
+ return await handle.result();
+ }
}
export function bindUserClient(
diff --git a/advanced/samples/typescript/wit/user-service/services.ts b/advanced/samples/typescript/wit/user-service/services.ts
index 568534224..344ac25f1 100644
--- a/advanced/samples/typescript/wit/user-service/services.ts
+++ b/advanced/samples/typescript/wit/user-service/services.ts
@@ -2,7 +2,13 @@
import * as workflow from "@temporalio/workflow";
import * as nexus from "nexus-rpc";
-import type { GetUserRequest, UpdateEmailRequest, NotifyRequest } from "./models";
+import type {
+ GetUserRequest,
+ UpdateEmailRequest,
+ NotifyRequest,
+ UserProfile,
+ GetProfileRequest,
+} from "./models";
import type { User } from "./resources";
import { bindUserClient } from "./resources";
@@ -10,6 +16,7 @@ export const userService = nexus.service("UserService", {
getUser: nexus.operation({ name: "GetUser" }),
updateEmail: nexus.operation({ name: "UpdateEmail" }),
notify: nexus.operation({ name: "Notify" }),
+ getProfile: nexus.operation({ name: "GetProfile" }),
});
export class UserServiceClient {
@@ -54,4 +61,13 @@ export class UserServiceClient {
): Promise> {
return await this.client.startOperation(userService.operations.notify, request);
}
+
+ /**
+ * @param request - Request for the operation.
+ */
+ public async getProfile(
+ request: GetProfileRequest,
+ ): Promise> {
+ return await this.client.startOperation(userService.operations.getProfile, request);
+ }
}
diff --git a/changelog/fixed/bound-bobcat.md b/changelog/fixed/bound-bobcat.md
new file mode 100644
index 000000000..0385d3f04
--- /dev/null
+++ b/changelog/fixed/bound-bobcat.md
@@ -0,0 +1 @@
+A resource method binds to an operation that returns a WIT record, in place of a stub.
diff --git a/src/planning/resource_resolution.rs b/src/planning/resource_resolution.rs
index 57cda5d4e..91c949ca7 100644
--- a/src/planning/resource_resolution.rs
+++ b/src/planning/resource_resolution.rs
@@ -429,6 +429,9 @@ fn resource_method_result_matches_operation(
operation_output_ref(operation) == Some(proto_name.as_str())
|| operation_output_resource_message_name(operation) == Some(proto_name.as_str())
}
+ TypeSpec::Record(record_name) => {
+ operation_output_ref(operation) == Some(record_name.as_str())
+ }
_ => false,
}
}