From aafa8dbae6b476b250739f5f643475378ff9c8f0 Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Fri, 23 Oct 2026 13:49:21 -0700 Subject: [PATCH 1/6] Made the stream-service create return a record that holds the stream. Today's compiler can't generate this shape, so this commit has only the WIT. --- advanced/samples/inputs/stream-service.wit | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/advanced/samples/inputs/stream-service.wit b/advanced/samples/inputs/stream-service.wit index 6ad7d10ac..8fa7d8dc1 100644 --- a/advanced/samples/inputs/stream-service.wit +++ b/advanced/samples/inputs/stream-service.wit @@ -118,9 +118,12 @@ interface stream-service { describe: func() -> describe-stream-response; } - // The stream binds from the stream field of the response. + // The stream binds from the stream field of the response, and the other fields stay beside it. /// @nexus.proto "streamsample.v1.CreateStreamResponse" typescript-import="#streamsample" - type create-stream-result = own<%stream>; + record create-stream-result { + %stream: own<%stream>, + created: option, + } /// @nexus.operation name="CreateStream" create-stream: func(request: create-stream-request) -> create-stream-result; From d31e11fb43566611574587921d9c3b62148f212e Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Fri, 23 Oct 2026 13:55:59 -0700 Subject: [PATCH 2/6] Let a proto-backed result record hold a resource in an own field. A create often answers with a handle and with facts about the create, such as whether it made a new object. An own result alone dropped those facts. --- GUIDE.md | 21 + advanced/samples/README.md | 11 + .../dotnet/Nexgen.DotNetExamples.csproj | 2 +- .../dotnet/Nexgen.DotNetWorkflowRun.csproj | 22 + .../dotnet/wit/stream-service/Models.cs | 83 ++-- .../dotnet/wit/stream-service/Operations.cs | 8 +- .../dotnet/wit/stream-service/Services.cs | 4 +- .../samples/dotnet/wit/workflow-run/Models.cs | 273 +++++++++++++ .../dotnet/wit/workflow-run/Operations.cs | 101 +++++ .../dotnet/wit/workflow-run/Resources.cs | 36 ++ .../dotnet/wit/workflow-run/Services.cs | 69 ++++ .../SystemNexusWorkflowOutboundInterceptor.cs | 107 +++++ .../go/workflowrun/workflowrunservice.go | 383 ++++++++++++++++++ advanced/samples/inputs/examples.toml | 5 + advanced/samples/inputs/workflow-run.wit | 114 ++++++ .../expected.toml | 5 + .../input.wit | 23 ++ .../wit/stream_service/_resources/__init__.py | 6 +- .../wit/stream_service/_resources/stream.py | 6 + .../python/wit/stream_service/models.py | 22 +- .../operations/create_stream.py | 20 +- .../python/wit/stream_service/services.py | 4 +- .../python/wit/workflow_run/__init__.py | 57 +++ .../wit/workflow_run/_resources/__init__.py | 13 + .../workflow_run/_resources/workflow_run.py | 63 +++ .../workflow_run/_system_nexus_interceptor.py | 142 +++++++ .../samples/python/wit/workflow_run/models.py | 280 +++++++++++++ .../wit/workflow_run/operations/__init__.py | 3 + .../wit/workflow_run/operations/start_run.py | 53 +++ .../python/wit/workflow_run/services.py | 24 ++ .../typescript/wit/stream-service/models.ts | 28 +- .../operations/create-stream.ts | 11 +- .../typescript/wit/stream-service/services.ts | 8 +- .../typescript/wit/workflow-run/index.ts | 10 + .../wit/workflow-run/interceptors.ts | 52 +++ .../typescript/wit/workflow-run/models.ts | 245 +++++++++++ .../wit/workflow-run/operations/cancel-run.ts | 29 ++ .../wit/workflow-run/operations/start-run.ts | 36 ++ .../typescript/wit/workflow-run/registry.ts | 12 + .../typescript/wit/workflow-run/resources.ts | 19 + .../typescript/wit/workflow-run/services.ts | 33 ++ changelog/added/holding-heron.md | 1 + src/generator/dotnet.rs | 164 +++++--- src/generator/go.rs | 24 +- src/generator/proto/go.rs | 89 +++- src/generator/python.rs | 137 ++++++- src/generator/typescript.rs | 89 +++- src/planning/authored_validation.rs | 25 +- src/planning/mod.rs | 23 +- src/planning/operation_lowering.rs | 100 +++++ src/planning/reachability.rs | 8 + src/planning/resource_resolution.rs | 100 ++++- src/planning/type_planning.rs | 19 + xtask/src/validate.rs | 6 + 54 files changed, 3067 insertions(+), 161 deletions(-) create mode 100644 advanced/samples/dotnet/Nexgen.DotNetWorkflowRun.csproj create mode 100644 advanced/samples/dotnet/wit/workflow-run/Models.cs create mode 100644 advanced/samples/dotnet/wit/workflow-run/Operations.cs create mode 100644 advanced/samples/dotnet/wit/workflow-run/Resources.cs create mode 100644 advanced/samples/dotnet/wit/workflow-run/Services.cs create mode 100644 advanced/samples/dotnet/wit/workflow-run/SystemNexusWorkflowOutboundInterceptor.cs create mode 100644 advanced/samples/go/workflowrun/workflowrunservice.go create mode 100644 advanced/samples/inputs/workflow-run.wit create mode 100644 advanced/samples/invalid/resources/result-record-holder-without-proto/expected.toml create mode 100644 advanced/samples/invalid/resources/result-record-holder-without-proto/input.wit create mode 100644 advanced/samples/python/wit/workflow_run/__init__.py create mode 100644 advanced/samples/python/wit/workflow_run/_resources/__init__.py create mode 100644 advanced/samples/python/wit/workflow_run/_resources/workflow_run.py create mode 100644 advanced/samples/python/wit/workflow_run/_system_nexus_interceptor.py create mode 100644 advanced/samples/python/wit/workflow_run/models.py create mode 100644 advanced/samples/python/wit/workflow_run/operations/__init__.py create mode 100644 advanced/samples/python/wit/workflow_run/operations/start_run.py create mode 100644 advanced/samples/python/wit/workflow_run/services.py create mode 100644 advanced/samples/typescript/wit/workflow-run/index.ts create mode 100644 advanced/samples/typescript/wit/workflow-run/interceptors.ts create mode 100644 advanced/samples/typescript/wit/workflow-run/models.ts create mode 100644 advanced/samples/typescript/wit/workflow-run/operations/cancel-run.ts create mode 100644 advanced/samples/typescript/wit/workflow-run/operations/start-run.ts create mode 100644 advanced/samples/typescript/wit/workflow-run/registry.ts create mode 100644 advanced/samples/typescript/wit/workflow-run/resources.ts create mode 100644 advanced/samples/typescript/wit/workflow-run/services.ts create mode 100644 changelog/added/holding-heron.md diff --git a/GUIDE.md b/GUIDE.md index 8ebe62945..f5da84e63 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -713,6 +713,27 @@ return StartedWorkflow( If a resource constructor field name cannot be found in either, the generator raises `InvalidResource`. +**A result record that holds the resource.** An operation can also return a +proto-backed record with one `own` field and other fields. The resource binds +from the record's message as above, and the other fields come from the fields of +the same name. The record is the API model only, and it declares only the fields +that its caller sees. Its message travels as a wire record that takes the proto +message's name. Python, TypeScript, .NET and Go generate it. In Python, the +record lives in its resource's module. + +```wit +/// @nexus.proto "acme.jobs.v1.CreateJobResponse" +record create-job-result { + job: own, + created: option, +} +``` + +```python +result = await handle # CreateJobResponse +return CreateJobResult(job=Job(job_id=result.job_id), created=result.created) +``` + ### Edge Cases and Error Conditions #### Same field name in constructor and method parameter diff --git a/advanced/samples/README.md b/advanced/samples/README.md index e6f7b763b..9b66d9c0b 100644 --- a/advanced/samples/README.md +++ b/advanced/samples/README.md @@ -148,6 +148,17 @@ records are proto-backed, as a stream service's are, on the sample proto in [`typescript/wit/stream-service/`](typescript/wit/stream-service/), [`typescript/tests/stream-service.test.ts`](typescript/tests/stream-service.test.ts) +### `workflow-run` + +A System Nexus operation that returns a proto-backed record that holds a +resource, next to the other fields of the same message. + +- WIT: [`inputs/workflow-run.wit`](inputs/workflow-run.wit) +- .NET: [`dotnet/wit/workflow-run/`](dotnet/wit/workflow-run/) +- Go: [`go/workflowrun/`](go/workflowrun/) +- Python: [`python/wit/workflow_run/`](python/wit/workflow_run/) +- TypeScript: [`typescript/wit/workflow-run/`](typescript/wit/workflow-run/) + ## Supporting files - [`inputs/deps/`](inputs/deps/): reusable Temporal semantic/common type WIT diff --git a/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj b/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj index 11a5e06ef..726e62aa9 100644 --- a/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj +++ b/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj @@ -14,7 +14,7 @@ - + diff --git a/advanced/samples/dotnet/Nexgen.DotNetWorkflowRun.csproj b/advanced/samples/dotnet/Nexgen.DotNetWorkflowRun.csproj new file mode 100644 index 000000000..575753194 --- /dev/null +++ b/advanced/samples/dotnet/Nexgen.DotNetWorkflowRun.csproj @@ -0,0 +1,22 @@ + + + net8.0 + 9.0 + enable + false + Nexgen.DotNetWorkflowRun + + + + + + + + + + + + + + diff --git a/advanced/samples/dotnet/wit/stream-service/Models.cs b/advanced/samples/dotnet/wit/stream-service/Models.cs index 226040f1e..1bc6175a3 100644 --- a/advanced/samples/dotnet/wit/stream-service/Models.cs +++ b/advanced/samples/dotnet/wit/stream-service/Models.cs @@ -10,6 +10,53 @@ namespace Nexgen.StreamService { + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(CreateStreamResponse.TransferTypeConverter))] + [GeneratedCode("nexgen", null)] + public record CreateStreamResponse + { + public StreamRef? Stream { get; init; } + public bool? Created { get; init; } + + internal static CreateStreamResponse FromTransferType(StreamSample.V1.CreateStreamResponse wire) + { + return new CreateStreamResponse() + { + Stream = wire.Stream == null ? null : StreamRef.FromTransferType(wire.Stream), + Created = wire.Created == false ? null : wire.Created, + }; + } + + internal StreamSample.V1.CreateStreamResponse ToTransferType() + { + var proto = new StreamSample.V1.CreateStreamResponse(); + if (Stream is { } stream) + { + proto.Stream = (StreamSample.V1.StreamRef)stream.ToTransferType(); + } + if (Created is { } created) + { + proto.Created = created; + } + return proto; + } + + /// + /// Converts this model to and from its generated transfer type. + /// + public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter + { + /// Gets the generated transfer type. + public System.Type TransferType => typeof(StreamSample.V1.CreateStreamResponse); + + /// Converts a model value to its transfer type. + public object? ToTransferType(object? value) => value is null ? null : ((CreateStreamResponse)value).ToTransferType(); + + /// Converts a transfer-type value to this model. + public object? FromTransferType(object? transferType) => transferType is null ? null : CreateStreamResponse.FromTransferType((StreamSample.V1.CreateStreamResponse)transferType); + } + + } + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(AppendRecord.TransferTypeConverter))] [GeneratedCode("nexgen", null)] public record AppendRecord @@ -286,45 +333,19 @@ public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTrans } - [Temporalio.Converters.TemporalTransferTypeConverter(typeof(CreateStreamResult.TransferTypeConverter))] [GeneratedCode("nexgen", null)] public record CreateStreamResult { - public StreamRef? Stream { get; init; } - - internal static CreateStreamResult FromTransferType(StreamSample.V1.CreateStreamResponse wire) - { - return new CreateStreamResult() - { - Stream = wire.Stream == null ? null : StreamRef.FromTransferType(wire.Stream), - }; - } - - internal StreamSample.V1.CreateStreamResponse ToTransferType() - { - var proto = new StreamSample.V1.CreateStreamResponse(); - if (Stream is { } stream) - { - proto.Stream = (StreamSample.V1.StreamRef)stream.ToTransferType(); - } - return proto; - } - /// - /// Converts this model to and from its generated transfer type. + /// Initializes a new instance of the class. /// - public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter + public CreateStreamResult(Stream stream) { - /// Gets the generated transfer type. - public System.Type TransferType => typeof(StreamSample.V1.CreateStreamResponse); - - /// Converts a model value to its transfer type. - public object? ToTransferType(object? value) => value is null ? null : ((CreateStreamResult)value).ToTransferType(); - - /// Converts a transfer-type value to this model. - public object? FromTransferType(object? transferType) => transferType is null ? null : CreateStreamResult.FromTransferType((StreamSample.V1.CreateStreamResponse)transferType); + Stream = stream; } + public Stream Stream { get; init; } + public bool? Created { get; init; } } [Temporalio.Converters.TemporalTransferTypeConverter(typeof(DescribeStreamRequest.TransferTypeConverter))] diff --git a/advanced/samples/dotnet/wit/stream-service/Operations.cs b/advanced/samples/dotnet/wit/stream-service/Operations.cs index 5f700c6d0..7c137b3fa 100644 --- a/advanced/samples/dotnet/wit/stream-service/Operations.cs +++ b/advanced/samples/dotnet/wit/stream-service/Operations.cs @@ -79,16 +79,16 @@ public static class Operations private const string StreamServiceEndpoint = "streams"; [GeneratedCode("nexgen", null)] - private static async Task CreateStreamAsync(CreateStreamRequest request) + private static async Task CreateStreamAsync(CreateStreamRequest request) { var client = Workflow.CreateNexusWorkflowClient(StreamServiceEndpoint); - var result = await client.ExecuteNexusOperationAsync(svc => svc.CreateStream(request)).ConfigureAwait(true); - return new Stream((result.Stream ?? throw new System.InvalidOperationException("CreateStream answered without `stream`"))); + var result = await client.ExecuteNexusOperationAsync(svc => svc.CreateStream(request)).ConfigureAwait(true); + return new CreateStreamResult(new Stream((result.Stream ?? throw new System.InvalidOperationException("CreateStream answered without `stream`")))) { Created = result.Created }; } /// Options for the operation. [GeneratedCode("nexgen", null)] - public static Task CreateStreamAsync(CreateStreamOptions? options = null) + public static Task CreateStreamAsync(CreateStreamOptions? options = null) { var request = new CreateStreamRequest() { diff --git a/advanced/samples/dotnet/wit/stream-service/Services.cs b/advanced/samples/dotnet/wit/stream-service/Services.cs index 478463df4..4c50e681c 100644 --- a/advanced/samples/dotnet/wit/stream-service/Services.cs +++ b/advanced/samples/dotnet/wit/stream-service/Services.cs @@ -19,7 +19,7 @@ internal interface IStreamService { [GeneratedCode("nexgen", null)] [NexusOperation("CreateStream")] - CreateStreamResult CreateStream(CreateStreamRequest request); + CreateStreamResponse CreateStream(CreateStreamRequest request); [GeneratedCode("nexgen", null)] [NexusOperation("AppendRecords")] @@ -71,7 +71,7 @@ internal static class NexgenOperationRegistry internal static IReadOnlyDictionary<(string Service, string Operation), INexgenOperationInfo> Operations { get; } = new Dictionary<(string Service, string Operation), INexgenOperationInfo> { - [("streamsample.v1.StreamService", "CreateStream")] = new NexgenOperationInfo( + [("streamsample.v1.StreamService", "CreateStream")] = new NexgenOperationInfo( StreamServiceServiceDefinition.Operations["CreateStream"]), [("streamsample.v1.StreamService", "AppendRecords")] = new NexgenOperationInfo( StreamServiceServiceDefinition.Operations["AppendRecords"]), diff --git a/advanced/samples/dotnet/wit/workflow-run/Models.cs b/advanced/samples/dotnet/wit/workflow-run/Models.cs new file mode 100644 index 000000000..60a65b703 --- /dev/null +++ b/advanced/samples/dotnet/wit/workflow-run/Models.cs @@ -0,0 +1,273 @@ +// +// Generated by nexgen latest. DO NOT EDIT! +#nullable enable + +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using Temporalio.Converters; +using Nexgen.Support; + +namespace Temporalio.Workflows +{ + + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(StartWorkflowExecutionResponse.TransferTypeConverter))] + [GeneratedCode("nexgen", null)] + public record StartWorkflowExecutionResponse + { + public string? RunId { get; init; } + public bool? Started { get; init; } + + internal static StartWorkflowExecutionResponse FromTransferType(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse wire) + { + return new StartWorkflowExecutionResponse() + { + RunId = string.IsNullOrEmpty(wire.RunId) ? null : wire.RunId, + Started = wire.Started == false ? null : wire.Started, + }; + } + + internal Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse ToTransferType() + { + var proto = new Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse(); + if (RunId is { } runId) + { + proto.RunId = runId; + } + if (Started is { } started) + { + proto.Started = started; + } + return proto; + } + + /// + /// Converts this model to and from its generated transfer type. + /// + public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter + { + /// Gets the generated transfer type. + public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse); + + /// Converts a model value to its transfer type. + public object? ToTransferType(object? value) => value is null ? null : ((StartWorkflowExecutionResponse)value).ToTransferType(); + + /// Converts a transfer-type value to this model. + public object? FromTransferType(object? transferType) => transferType is null ? null : StartWorkflowExecutionResponse.FromTransferType((Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse)transferType); + } + + } + + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(CancelRunRequest.TransferTypeConverter))] + [GeneratedCode("nexgen", null)] + public record CancelRunRequest + { + /// + /// Initializes a new instance of the class. + /// + public CancelRunRequest(WorkflowExecution workflowExecution, string @namespace) + { + WorkflowExecution = workflowExecution; + Namespace = @namespace; + } + + public WorkflowExecution WorkflowExecution { get; init; } + public string? Reason { get; init; } + public string Namespace { get; init; } + + internal static CancelRunRequest FromTransferType(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest wire) + { + return new CancelRunRequest(WorkflowExecution.FromTransferType(wire.WorkflowExecution), wire.Namespace) + { + Reason = string.IsNullOrEmpty(wire.Reason) ? null : wire.Reason, + }; + } + + internal Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest ToTransferType() + { + var proto = new Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest(); + proto.Namespace = Namespace; + proto.WorkflowExecution = (Temporalio.Api.Common.V1.WorkflowExecution)WorkflowExecution.ToTransferType(); + if (Reason is { } reason) + { + proto.Reason = reason; + } + return proto; + } + + /// + /// Converts this model to and from its generated transfer type. + /// + public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter + { + /// Gets the generated transfer type. + public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest); + + /// Converts a model value to its transfer type. + public object? ToTransferType(object? value) => value is null ? null : ((CancelRunRequest)value).ToTransferType(); + + /// Converts a transfer-type value to this model. + public object? FromTransferType(object? transferType) => transferType is null ? null : CancelRunRequest.FromTransferType((Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest)transferType); + } + + } + + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(CancelRunResponse.TransferTypeConverter))] + [GeneratedCode("nexgen", null)] + public record CancelRunResponse + { + internal static CancelRunResponse FromTransferType(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse wire) + { + return new CancelRunResponse(); + } + + internal Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse ToTransferType() + { + var proto = new Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse(); + return proto; + } + + /// + /// Converts this model to and from its generated transfer type. + /// + public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter + { + /// Gets the generated transfer type. + public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse); + + /// Converts a model value to its transfer type. + public object? ToTransferType(object? value) => value is null ? null : ((CancelRunResponse)value).ToTransferType(); + + /// Converts a transfer-type value to this model. + public object? FromTransferType(object? transferType) => transferType is null ? null : CancelRunResponse.FromTransferType((Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse)transferType); + } + + } + + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(StartRunRequest.TransferTypeConverter))] + [GeneratedCode("nexgen", null)] + public record StartRunRequest + { + /// + /// Initializes a new instance of the class. + /// + public StartRunRequest(string workflow, string workflowId, string taskQueue, string @namespace) + { + Workflow = workflow; + WorkflowId = workflowId; + TaskQueue = taskQueue; + Namespace = @namespace; + } + + public string Workflow { get; init; } + public string WorkflowId { get; init; } + public string TaskQueue { get; init; } + public System.TimeSpan? WorkflowStartDelay { get; init; } + public string Namespace { get; init; } + + internal static StartRunRequest FromTransferType(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest wire) + { + return new StartRunRequest(Nexgen.Support.ProtoExtensions.FromWorkflowTypeProto(wire.WorkflowType), wire.WorkflowId, Nexgen.Support.ProtoExtensions.FromTaskQueueProto(wire.TaskQueue), wire.Namespace) + { + WorkflowStartDelay = wire.WorkflowStartDelay == null ? null : Nexgen.Support.ProtoExtensions.FromDurationProto(wire.WorkflowStartDelay), + }; + } + + internal Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest ToTransferType() + { + var proto = new Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest(); + proto.Namespace = Namespace; + proto.WorkflowType = Nexgen.Support.ProtoExtensions.ToWorkflowTypeProto(Workflow); + proto.WorkflowId = WorkflowId; + proto.TaskQueue = Nexgen.Support.ProtoExtensions.ToTaskQueueProto(TaskQueue); + if (WorkflowStartDelay is { } workflowStartDelay) + { + proto.WorkflowStartDelay = workflowStartDelay.ToProto(); + } + return proto; + } + + /// + /// Converts this model to and from its generated transfer type. + /// + public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter + { + /// Gets the generated transfer type. + public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest); + + /// Converts a model value to its transfer type. + public object? ToTransferType(object? value) => value is null ? null : ((StartRunRequest)value).ToTransferType(); + + /// Converts a transfer-type value to this model. + public object? FromTransferType(object? transferType) => transferType is null ? null : StartRunRequest.FromTransferType((Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest)transferType); + } + + } + + [GeneratedCode("nexgen", null)] + public record StartRunResult + { + /// + /// Initializes a new instance of the class. + /// + public StartRunResult(WorkflowRun run) + { + Run = run; + } + + public WorkflowRun Run { get; init; } + public bool? Started { get; init; } + } + + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(WorkflowExecution.TransferTypeConverter))] + [GeneratedCode("nexgen", null)] + public record WorkflowExecution + { + /// + /// Initializes a new instance of the class. + /// + public WorkflowExecution(string workflowId) + { + WorkflowId = workflowId; + } + + public string WorkflowId { get; init; } + public string? RunId { get; init; } + + internal static WorkflowExecution FromTransferType(Temporalio.Api.Common.V1.WorkflowExecution wire) + { + return new WorkflowExecution(wire.WorkflowId) + { + RunId = string.IsNullOrEmpty(wire.RunId) ? null : wire.RunId, + }; + } + + internal Temporalio.Api.Common.V1.WorkflowExecution ToTransferType() + { + var proto = new Temporalio.Api.Common.V1.WorkflowExecution(); + proto.WorkflowId = WorkflowId; + if (RunId is { } runId) + { + proto.RunId = runId; + } + return proto; + } + + /// + /// Converts this model to and from its generated transfer type. + /// + public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter + { + /// Gets the generated transfer type. + public System.Type TransferType => typeof(Temporalio.Api.Common.V1.WorkflowExecution); + + /// Converts a model value to its transfer type. + public object? ToTransferType(object? value) => value is null ? null : ((WorkflowExecution)value).ToTransferType(); + + /// Converts a transfer-type value to this model. + public object? FromTransferType(object? transferType) => transferType is null ? null : WorkflowExecution.FromTransferType((Temporalio.Api.Common.V1.WorkflowExecution)transferType); + } + + } + +} diff --git a/advanced/samples/dotnet/wit/workflow-run/Operations.cs b/advanced/samples/dotnet/wit/workflow-run/Operations.cs new file mode 100644 index 000000000..eb89a9048 --- /dev/null +++ b/advanced/samples/dotnet/wit/workflow-run/Operations.cs @@ -0,0 +1,101 @@ +// +// Generated by nexgen latest. DO NOT EDIT! +#nullable enable + +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Google.Protobuf.WellKnownTypes; +using Temporalio.Workflows; +using Nexgen.Support; + +namespace Temporalio.Workflows +{ + + [GeneratedCode("nexgen", null)] + public class StartRunOptions + { + /// + /// Initializes a new instance of the class. + /// + public StartRunOptions(string workflow, string workflowId, string taskQueue) + { + Workflow = workflow; + WorkflowId = workflowId; + TaskQueue = taskQueue; + } + + public string Workflow { get; set; } + public string WorkflowId { get; set; } + public string TaskQueue { get; set; } + public System.TimeSpan? WorkflowStartDelay { get; set; } + } + + [GeneratedCode("nexgen", null)] + public class CancelRunOptions + { + /// + /// Initializes a new instance of the class. + /// + public CancelRunOptions(WorkflowExecution workflowExecution) + { + WorkflowExecution = workflowExecution; + } + + public WorkflowExecution WorkflowExecution { get; set; } + public string? Reason { get; set; } + } + + /// + /// Provides generated workflow operation helpers. + /// + [GeneratedCode("nexgen", null)] + public static class Operations + { + [GeneratedCode("nexgen", null)] + private const string WorkflowRunServiceEndpoint = "temporal-system"; + + [GeneratedCode("nexgen", null)] + private static async Task StartRunAsync(StartRunRequest request) + { + var client = Workflow.CreateNexusWorkflowClient(WorkflowRunServiceEndpoint); + var result = await client.ExecuteNexusOperationAsync(svc => svc.StartRun(request)).ConfigureAwait(true); + return new StartRunResult(new WorkflowRun(request.WorkflowId, string.IsNullOrEmpty(result.RunId) ? null : result.RunId)) { Started = result.Started }; + } + + /// Options for the operation. + [GeneratedCode("nexgen", null)] + public static Task StartRunAsync(StartRunOptions options) + { + var request = new StartRunRequest(options.Workflow, options.WorkflowId, options.TaskQueue, Nexgen.Support.TemporalWorkflowContext.WorkflowNamespace()) + { + WorkflowStartDelay = options.WorkflowStartDelay, + }; + return StartRunAsync(request); + } + + [GeneratedCode("nexgen", null)] + private static async Task CancelRunAsync(CancelRunRequest request) + { + var client = Workflow.CreateNexusWorkflowClient(WorkflowRunServiceEndpoint); + var result = await client.ExecuteNexusOperationAsync(svc => svc.CancelRun(request)).ConfigureAwait(true); + return result; + } + + /// Options for the operation. + [GeneratedCode("nexgen", null)] + public static Task CancelRunAsync(CancelRunOptions options) + { + var request = new CancelRunRequest(options.WorkflowExecution, Nexgen.Support.TemporalWorkflowContext.WorkflowNamespace()) + { + Reason = options.Reason, + }; + return CancelRunAsync(request); + } + + } + +} diff --git a/advanced/samples/dotnet/wit/workflow-run/Resources.cs b/advanced/samples/dotnet/wit/workflow-run/Resources.cs new file mode 100644 index 000000000..385a9cc3b --- /dev/null +++ b/advanced/samples/dotnet/wit/workflow-run/Resources.cs @@ -0,0 +1,36 @@ +// +// Generated by nexgen latest. DO NOT EDIT! +#nullable enable + +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Threading.Tasks; +using Temporalio.Workflows; +using Nexgen.Support; + +namespace Temporalio.Workflows +{ + + [GeneratedCode("nexgen", null)] + public class WorkflowRun + { + public WorkflowRun(string workflowId, string? runId) + { + WorkflowId = workflowId; + RunId = runId; + } + + public string WorkflowId { get; } + public string? RunId { get; } + + [GeneratedCode("nexgen", null)] + public Task CancelAsync(string? reason) + { + var request = new CancelRunOptions(new WorkflowExecution(WorkflowId) { RunId = RunId }) { Reason = reason }; + return Operations.CancelRunAsync(request); + } + + } + +} diff --git a/advanced/samples/dotnet/wit/workflow-run/Services.cs b/advanced/samples/dotnet/wit/workflow-run/Services.cs new file mode 100644 index 000000000..e125ea00a --- /dev/null +++ b/advanced/samples/dotnet/wit/workflow-run/Services.cs @@ -0,0 +1,69 @@ +// +// Generated by nexgen latest. DO NOT EDIT! +#nullable enable + +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Threading.Tasks; +using NexusRpc; +using Temporalio.Converters; +using Nexgen.Support; + +namespace Temporalio.Workflows +{ + + [GeneratedCode("nexgen", null)] + [NexusService("temporal.example.v1.WorkflowRunService")] + internal interface IWorkflowRunService + { + [GeneratedCode("nexgen", null)] + [NexusOperation("StartRun")] + StartWorkflowExecutionResponse StartRun(StartRunRequest request); + + [GeneratedCode("nexgen", null)] + [NexusOperation("CancelRun")] + CancelRunResponse CancelRun(CancelRunRequest request); + + } + + internal interface INexgenOperationInfo + { + OperationDefinition Operation { get; } + + Func? SerializationContext { get; } + } + + internal sealed class NexgenOperationInfo : INexgenOperationInfo + { + internal NexgenOperationInfo( + OperationDefinition operation, + Func? serializationContext = null) + { + Operation = operation; + SerializationContext = serializationContext; + } + + internal OperationDefinition Operation { get; } + + OperationDefinition INexgenOperationInfo.Operation => Operation; + + internal Func? SerializationContext { get; } + + Func? INexgenOperationInfo.SerializationContext => SerializationContext == null ? null : request => SerializationContext((TRequest)request); + } + + internal static class NexgenOperationRegistry + { + private static readonly ServiceDefinition WorkflowRunServiceServiceDefinition = ServiceDefinition.FromType(); + + internal static IReadOnlyDictionary<(string Service, string Operation), INexgenOperationInfo> Operations { get; } = new Dictionary<(string Service, string Operation), INexgenOperationInfo> + { + [("temporal.example.v1.WorkflowRunService", "StartRun")] = new NexgenOperationInfo( + WorkflowRunServiceServiceDefinition.Operations["StartRun"]), + [("temporal.example.v1.WorkflowRunService", "CancelRun")] = new NexgenOperationInfo( + WorkflowRunServiceServiceDefinition.Operations["CancelRun"]), + }; + } + +} diff --git a/advanced/samples/dotnet/wit/workflow-run/SystemNexusWorkflowOutboundInterceptor.cs b/advanced/samples/dotnet/wit/workflow-run/SystemNexusWorkflowOutboundInterceptor.cs new file mode 100644 index 000000000..34588a30d --- /dev/null +++ b/advanced/samples/dotnet/wit/workflow-run/SystemNexusWorkflowOutboundInterceptor.cs @@ -0,0 +1,107 @@ +// +// Generated by nexgen latest. DO NOT EDIT! +#nullable enable + +using System.CodeDom.Compiler; +using System.Threading.Tasks; +using Temporalio.Workflows; + +namespace Temporalio.Worker.Interceptors +{ + + public partial class WorkflowOutboundInterceptor + { + /// + /// Intercept the StartRun operation. + /// + /// Request for the operation. + /// Operation handle. + [GeneratedCode("nexgen", null)] + public virtual Task> StartRunAsync(StartRunRequest request) => Next.StartRunAsync(request); + + /// + /// Intercept the CancelRun operation. + /// + /// Request for the operation. + /// Operation handle. + [GeneratedCode("nexgen", null)] + public virtual Task> CancelRunAsync(CancelRunRequest request) => Next.CancelRunAsync(request); + + } + +} + +namespace Temporalio.Worker +{ + + internal partial class WorkflowInstance + { + [GeneratedCode("nexgen", null)] + private Task> StartSystemNexusOperationAsync( + string service, + string operationName, + object? arg) + { + if (service == "temporal.example.v1.WorkflowRunService" && + operationName == "StartRun") + { + if (arg is not StartRunRequest request) + { + throw new System.ArgumentException("System Nexus operation expects a StartRunRequest request.", nameof(arg)); + } + if (typeof(TResult) != typeof(StartWorkflowExecutionResponse)) + { + throw new System.ArgumentException("System Nexus operation expects a StartWorkflowExecutionResponse result."); + } + var handle = outbound.Value.StartRunAsync(request); + return (Task>)(object)handle; + } + if (service == "temporal.example.v1.WorkflowRunService" && + operationName == "CancelRun") + { + if (arg is not CancelRunRequest request) + { + throw new System.ArgumentException("System Nexus operation expects a CancelRunRequest request.", nameof(arg)); + } + if (typeof(TResult) != typeof(CancelRunResponse)) + { + throw new System.ArgumentException("System Nexus operation expects a CancelRunResponse result."); + } + var handle = outbound.Value.CancelRunAsync(request); + return (Task>)(object)handle; + } + return outbound.Value.ScheduleSystemNexusOperationAsync(new( + Service: service, + Operation: new( + operationName, + arg?.GetType() ?? typeof(object), + typeof(TResult)), + Arg: arg, + Headers: null)); + } + + internal partial class OutboundImpl + { + [GeneratedCode("nexgen", null)] + public override Task> StartRunAsync(StartRunRequest request) => instance.outbound.Value.ScheduleSystemNexusOperationAsync(new( + Service: "temporal.example.v1.WorkflowRunService", + Operation: new( + "StartRun", + typeof(StartRunRequest), + typeof(StartWorkflowExecutionResponse)), + Arg: request, + Headers: null)); + + [GeneratedCode("nexgen", null)] + public override Task> CancelRunAsync(CancelRunRequest request) => instance.outbound.Value.ScheduleSystemNexusOperationAsync(new( + Service: "temporal.example.v1.WorkflowRunService", + Operation: new( + "CancelRun", + typeof(CancelRunRequest), + typeof(CancelRunResponse)), + Arg: request, + Headers: null)); + + } + } +} diff --git a/advanced/samples/go/workflowrun/workflowrunservice.go b/advanced/samples/go/workflowrun/workflowrunservice.go new file mode 100644 index 000000000..10cad9dd8 --- /dev/null +++ b/advanced/samples/go/workflowrun/workflowrunservice.go @@ -0,0 +1,383 @@ +// Code generated by nexgen latest. DO NOT EDIT. + +package workflowrun + +import ( + "time" + + common "go.temporal.io/api/common/v1" + workflowservice "go.temporal.io/api/workflowservice/v1" + support "go.temporal.io/sdk/advanced/samples/go/systemsupport" + "go.temporal.io/sdk/converter" + "go.temporal.io/sdk/internal" + commonconverter "go.temporal.io/sdk/internal/common/converter" + "go.temporal.io/sdk/workflow" +) + +// --- Datatypes --- + +type startRunRequest struct { + Workflow string + WorkflowID string + TaskQueue string + WorkflowStartDelay *time.Duration + Namespace string +} + +func (m startRunRequest) GetWorkflow() string { + return m.Workflow +} + +func (m startRunRequest) GetWorkflowID() string { + return m.WorkflowID +} + +func (m startRunRequest) GetTaskQueue() string { + return m.TaskQueue +} + +func (m startRunRequest) GetWorkflowStartDelay() *time.Duration { + return m.WorkflowStartDelay +} + +func (m startRunRequest) GetNamespace() string { + return m.Namespace +} + +func startRunRequestToProto(cfg *commonconverter.SystemNexusConfig, m *startRunRequest) (*workflowservice.StartWorkflowExecutionRequest, error) { + message := &workflowservice.StartWorkflowExecutionRequest{} + { + converted, err := support.WorkflowTypeToProto(cfg.DataConverter, &m.Workflow) + if err != nil { + return nil, err + } + message.WorkflowType = converted + } + message.WorkflowId = m.WorkflowID + { + converted, err := support.TaskQueueToProto(cfg.DataConverter, &m.TaskQueue) + if err != nil { + return nil, err + } + message.TaskQueue = converted + } + { + converted, err := support.DurationToProto(cfg.DataConverter, m.WorkflowStartDelay) + if err != nil { + return nil, err + } + message.WorkflowStartDelay = converted + } + message.Namespace = m.Namespace + return message, nil +} + +func startRunRequestFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.StartWorkflowExecutionRequest, out *startRunRequest) error { + *out = startRunRequest{} + { + converted, err := support.WorkflowTypeFromProto(cfg.DataConverter, proto.GetWorkflowType()) + if err != nil { + return err + } + if converted != nil { + out.Workflow = *converted + } + } + out.WorkflowID = proto.GetWorkflowId() + { + converted, err := support.TaskQueueFromProto(cfg.DataConverter, proto.GetTaskQueue()) + if err != nil { + return err + } + if converted != nil { + out.TaskQueue = *converted + } + } + { + converted, err := support.DurationFromProto(cfg.DataConverter, proto.GetWorkflowStartDelay()) + if err != nil { + return err + } + out.WorkflowStartDelay = converted + } + out.Namespace = proto.GetNamespace() + return nil +} + +func (startRunRequest) TransferTypeConverter() (converter.TransferTypeConverter, error) { + return commonconverter.NewSystemNexusTransferTypeConverter( + startRunRequestToProto, + startRunRequestFromProto, + ) +} + +type cancelRunRequest struct { + WorkflowExecution WorkflowExecution + Reason *string + Namespace string +} + +func (m cancelRunRequest) GetWorkflowExecution() WorkflowExecution { + return m.WorkflowExecution +} + +func (m cancelRunRequest) GetReason() *string { + return m.Reason +} + +func (m cancelRunRequest) GetNamespace() string { + return m.Namespace +} + +func cancelRunRequestToProto(cfg *commonconverter.SystemNexusConfig, m *cancelRunRequest) (*workflowservice.RequestCancelWorkflowExecutionRequest, error) { + message := &workflowservice.RequestCancelWorkflowExecutionRequest{} + { + converted, err := workflowExecutionToProto(cfg, &m.WorkflowExecution) + if err != nil { + return nil, err + } + message.WorkflowExecution = converted + } + if m.Reason != nil { + message.Reason = (*m.Reason) + } + message.Namespace = m.Namespace + return message, nil +} + +func cancelRunRequestFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.RequestCancelWorkflowExecutionRequest, out *cancelRunRequest) error { + *out = cancelRunRequest{} + if err := workflowExecutionFromProto(cfg, proto.GetWorkflowExecution(), &out.WorkflowExecution); err != nil { + return err + } + { + converted := proto.GetReason() + out.Reason = &converted + } + out.Namespace = proto.GetNamespace() + return nil +} + +func (cancelRunRequest) TransferTypeConverter() (converter.TransferTypeConverter, error) { + return commonconverter.NewSystemNexusTransferTypeConverter( + cancelRunRequestToProto, + cancelRunRequestFromProto, + ) +} + +type startWorkflowExecutionResponse struct { + RunID *string + Started *bool +} + +func (m startWorkflowExecutionResponse) GetRunID() *string { + return m.RunID +} + +func (m startWorkflowExecutionResponse) GetStarted() *bool { + return m.Started +} + +func startWorkflowExecutionResponseToProto(cfg *commonconverter.SystemNexusConfig, m *startWorkflowExecutionResponse) (*workflowservice.StartWorkflowExecutionResponse, error) { + message := &workflowservice.StartWorkflowExecutionResponse{} + if m.RunID != nil { + message.RunId = (*m.RunID) + } + if m.Started != nil { + message.Started = (*m.Started) + } + return message, nil +} + +func startWorkflowExecutionResponseFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.StartWorkflowExecutionResponse, out *startWorkflowExecutionResponse) error { + *out = startWorkflowExecutionResponse{} + { + converted := proto.GetRunId() + out.RunID = &converted + } + { + converted := proto.GetStarted() + out.Started = &converted + } + return nil +} + +// --- Resources --- + +type WorkflowRun struct { + // WorkflowID - Required. + WorkflowID string + // RunID - Optional. + RunID *string +} + +func NewWorkflowRun(workflowID string, runID string) WorkflowRun { + var runIDPtr *string + if runID != "" { + runIDPtr = &runID + } + return WorkflowRun{ + WorkflowID: workflowID, + RunID: runIDPtr, + } +} + +func (u *WorkflowRun) Cancel(ctx workflow.Context, reason string) workflow.Future { + var reasonPtr *string + if reason != "" { + reasonPtr = &reason + } + return cancelRun(ctx, cancelRunRequest{WorkflowExecution: WorkflowExecution{WorkflowID: u.WorkflowID, RunID: u.RunID}, Reason: reasonPtr}) +} + +// --- Operations (internal) --- + +func startRun(ctx workflow.Context, request startRunRequest) workflow.Future { + request.Namespace = support.WorkflowNamespace(ctx) + c := internal.NewSystemNexusClient("temporal.example.v1.WorkflowRunService") + fut := c.ExecuteOperation(ctx, "StartRun", request, workflow.NexusOperationOptions{}) + result, resultSettable := workflow.NewFuture(ctx) + workflow.Go(ctx, func(ctx workflow.Context) { + var result workflowservice.StartWorkflowExecutionResponse + if err := fut.Get(ctx, &result); err != nil { + resultSettable.SetError(err) + return + } + var heldStarted *bool + if value := result.GetStarted(); value != false { + heldStarted = &value + } + value := StartRunResult{Run: NewWorkflowRun(request.WorkflowID, result.GetRunId()), Started: heldStarted} + resultSettable.Set(value, nil) + }) + return result +} + +func cancelRun(ctx workflow.Context, request cancelRunRequest) workflow.Future { + request.Namespace = support.WorkflowNamespace(ctx) + c := internal.NewSystemNexusClient("temporal.example.v1.WorkflowRunService") + fut := c.ExecuteOperation(ctx, "CancelRun", request, workflow.NexusOperationOptions{}) + result, resultSettable := workflow.NewFuture(ctx) + workflow.Go(ctx, func(ctx workflow.Context) { + var value CancelRunResponse + if err := fut.Get(ctx, &value); err != nil { + resultSettable.SetError(err) + return + } + resultSettable.Set(value, nil) + }) + return result +} + +// --- Operations (public API) --- + +type WorkflowExecution struct { + // WorkflowID - Required. + WorkflowID string + // RunID - Optional. + RunID *string +} + +func (m WorkflowExecution) GetWorkflowID() string { + return m.WorkflowID +} + +func (m WorkflowExecution) GetRunID() *string { + return m.RunID +} + +func workflowExecutionToProto(cfg *commonconverter.SystemNexusConfig, m *WorkflowExecution) (*common.WorkflowExecution, error) { + message := &common.WorkflowExecution{} + message.WorkflowId = m.WorkflowID + if m.RunID != nil { + message.RunId = (*m.RunID) + } + return message, nil +} + +func workflowExecutionFromProto(cfg *commonconverter.SystemNexusConfig, proto *common.WorkflowExecution, out *WorkflowExecution) error { + *out = WorkflowExecution{} + out.WorkflowID = proto.GetWorkflowId() + { + converted := proto.GetRunId() + out.RunID = &converted + } + return nil +} + +type CancelRunResponse struct { +} + +func cancelRunResponseToProto(cfg *commonconverter.SystemNexusConfig, m *CancelRunResponse) (*workflowservice.RequestCancelWorkflowExecutionResponse, error) { + message := &workflowservice.RequestCancelWorkflowExecutionResponse{} + return message, nil +} + +func cancelRunResponseFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.RequestCancelWorkflowExecutionResponse, out *CancelRunResponse) error { + *out = CancelRunResponse{} + return nil +} + +func (CancelRunResponse) TransferTypeConverter() (converter.TransferTypeConverter, error) { + return commonconverter.NewSystemNexusTransferTypeConverter( + cancelRunResponseToProto, + cancelRunResponseFromProto, + ) +} + +type StartRunResult struct { + // Run - Required. + Run WorkflowRun + // Started - Optional. + Started *bool +} + +func (m StartRunResult) GetRun() WorkflowRun { + return m.Run +} + +func (m StartRunResult) GetStarted() *bool { + return m.Started +} + +type StartRunOptions struct { + // Workflow - Required. + Workflow string + // WorkflowID - Required. + WorkflowID string + // TaskQueue - Required. + TaskQueue string + // WorkflowStartDelay - Optional. + WorkflowStartDelay time.Duration +} + +func StartRun(ctx workflow.Context, opts StartRunOptions) workflow.Future { + var workflowStartDelay *time.Duration + if opts.WorkflowStartDelay != 0 { + workflowStartDelay = &opts.WorkflowStartDelay + } + return startRun(ctx, startRunRequest{ + Workflow: opts.Workflow, + WorkflowID: opts.WorkflowID, + TaskQueue: opts.TaskQueue, + WorkflowStartDelay: workflowStartDelay, + }) +} + +type CancelRunOptions struct { + // WorkflowExecution - Required. + WorkflowExecution WorkflowExecution + // Reason - Optional. + Reason string +} + +func CancelRun(ctx workflow.Context, opts CancelRunOptions) workflow.Future { + var reason *string + if opts.Reason != "" { + reason = &opts.Reason + } + return cancelRun(ctx, cancelRunRequest{ + WorkflowExecution: opts.WorkflowExecution, + Reason: reason, + }) +} diff --git a/advanced/samples/inputs/examples.toml b/advanced/samples/inputs/examples.toml index a41cf5a86..f4f999946 100644 --- a/advanced/samples/inputs/examples.toml +++ b/advanced/samples/inputs/examples.toml @@ -26,6 +26,11 @@ descriptors = "temporal" languages = ["python", "dotnet", "typescript"] descriptors = "temporal" +[workflow-run] +system_nexus = true +languages = ["python", "typescript", "dotnet", "go"] +descriptors = "temporal" + [stream-service] languages = ["python", "typescript", "dotnet"] # The sample proto imports the Temporal API messages, so it needs both sets. diff --git a/advanced/samples/inputs/workflow-run.wit b/advanced/samples/inputs/workflow-run.wit new file mode 100644 index 000000000..cded4e3ab --- /dev/null +++ b/advanced/samples/inputs/workflow-run.wit @@ -0,0 +1,114 @@ +package temporal:nexus@1.0.0; + +world system { + export workflow-run-service; +} + +/// @nexus.endpoint "temporal-system" go="__temporal_system" python="__temporal_system" +/// @nexus.service-name "temporal.example.v1.WorkflowRunService" +interface workflow-run-service { + use nexus:temporal-types/model@1.0.0.{duration, placeholder, task-queue, workflow-type}; + + /// @nexus.proto "temporal.api.common.v1.WorkflowExecution" typescript-import="@temporalio/proto" + record workflow-execution { + workflow-id: string, + run-id: option, + } + + resource workflow-run { + constructor(workflow-id: string, run-id: option); + + /// @nexus.operation "cancel-run" + cancel: func(reason: option); + } + + /// @nexus.proto "temporal.api.workflowservice.v1.StartWorkflowExecutionRequest" typescript-import="@temporalio/proto" + record start-run-request { + /// @nexus.proto-field "workflow_type" + workflow: workflow-type, + /// @nexus.omit + input: placeholder, + workflow-id: string, + task-queue: task-queue, + workflow-start-delay: option, + /// @nexus.source python="workflow_namespace()" typescript="workflowNamespace()" go="WorkflowNamespace(ctx)" dotnet="TemporalWorkflowContext.WorkflowNamespace()" + namespace: string, + /// @nexus.omit + workflow-execution-timeout: placeholder, + /// @nexus.omit + workflow-run-timeout: placeholder, + /// @nexus.omit + workflow-task-timeout: placeholder, + /// @nexus.omit + identity: placeholder, + /// @nexus.omit + request-id: placeholder, + /// @nexus.omit + workflow-id-reuse-policy: placeholder, + /// @nexus.omit + workflow-id-conflict-policy: placeholder, + /// @nexus.omit + retry-policy: placeholder, + /// @nexus.omit + cron-schedule: placeholder, + /// @nexus.omit + memo: placeholder, + /// @nexus.omit + search-attributes: placeholder, + /// @nexus.omit + header: placeholder, + /// @nexus.omit + request-eager-execution: placeholder, + /// @nexus.omit + continued-failure: placeholder, + /// @nexus.omit + last-completion-result: placeholder, + /// @nexus.omit + completion-callbacks: placeholder, + /// @nexus.omit + user-metadata: placeholder, + /// @nexus.omit + links: placeholder, + /// @nexus.omit + versioning-override: placeholder, + /// @nexus.omit + on-conflict-options: placeholder, + /// @nexus.omit + priority: placeholder, + /// @nexus.omit + eager-worker-deployment-options: placeholder, + /// @nexus.omit + time-skipping-config: placeholder, + } + + // The handle binds from the response, and the other fields stay beside it. + /// @nexus.proto "temporal.api.workflowservice.v1.StartWorkflowExecutionResponse" typescript-import="@temporalio/proto" + record start-run-result { + run: own, + started: option, + } + + /// @nexus.proto "temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest" typescript-import="@temporalio/proto" + record cancel-run-request { + /// @nexus.source python="workflow_namespace()" typescript="workflowNamespace()" go="WorkflowNamespace(ctx)" dotnet="TemporalWorkflowContext.WorkflowNamespace()" + namespace: string, + workflow-execution: workflow-execution, + reason: option, + /// @nexus.omit + identity: placeholder, + /// @nexus.omit + request-id: placeholder, + /// @nexus.omit + first-execution-run-id: placeholder, + /// @nexus.omit + links: placeholder, + } + + /// @nexus.proto "temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionResponse" typescript-import="@temporalio/proto" + record cancel-run-response { + } + + start-run: func(request: start-run-request) -> start-run-result; + + cancel-run: func(request: cancel-run-request) -> cancel-run-response; +} diff --git a/advanced/samples/invalid/resources/result-record-holder-without-proto/expected.toml b/advanced/samples/invalid/resources/result-record-holder-without-proto/expected.toml new file mode 100644 index 000000000..0da14ccf4 --- /dev/null +++ b/advanced/samples/invalid/resources/result-record-holder-without-proto/expected.toml @@ -0,0 +1,5 @@ +descriptors = "temporal" + +[error] +kind = "invalid-resource" +contains = ["result record `StartRunResult`", "so it needs `@nexus.proto`"] diff --git a/advanced/samples/invalid/resources/result-record-holder-without-proto/input.wit b/advanced/samples/invalid/resources/result-record-holder-without-proto/input.wit new file mode 100644 index 000000000..75a22502f --- /dev/null +++ b/advanced/samples/invalid/resources/result-record-holder-without-proto/input.wit @@ -0,0 +1,23 @@ +package test:holder@1.0.0; + +world system { + export api; +} + +/// @nexus.endpoint "runs" +interface api { + resource workflow-run { + constructor(run-id: option); + } + + /// @nexus.proto "temporal.api.workflowservice.v1.GetSystemInfoRequest" + record start-run-request { + } + + record start-run-result { + run: own, + started: option, + } + + start-run: func(request: start-run-request) -> start-run-result; +} diff --git a/advanced/samples/python/wit/stream_service/_resources/__init__.py b/advanced/samples/python/wit/stream_service/_resources/__init__.py index 06bb70ddd..4cb791aed 100644 --- a/advanced/samples/python/wit/stream_service/_resources/__init__.py +++ b/advanced/samples/python/wit/stream_service/_resources/__init__.py @@ -2,8 +2,12 @@ from __future__ import annotations -from .stream import Stream +from .stream import ( + Stream, + CreateStreamResult, +) __all__ = [ "Stream", + "CreateStreamResult", ] diff --git a/advanced/samples/python/wit/stream_service/_resources/stream.py b/advanced/samples/python/wit/stream_service/_resources/stream.py index 0b0aa9437..a045fed29 100644 --- a/advanced/samples/python/wit/stream_service/_resources/stream.py +++ b/advanced/samples/python/wit/stream_service/_resources/stream.py @@ -76,6 +76,12 @@ async def describe( return await handle +@dataclasses.dataclass(slots=True) +class CreateStreamResult: + stream: Stream + created: bool | None = None + + async def _append_records( request: AppendRecordsRequest, ) -> temporalio.workflow.NexusOperationHandle[AppendRecordsResponse,]: diff --git a/advanced/samples/python/wit/stream_service/models.py b/advanced/samples/python/wit/stream_service/models.py index fcf9313b9..8d0a1b48d 100644 --- a/advanced/samples/python/wit/stream_service/models.py +++ b/advanced/samples/python/wit/stream_service/models.py @@ -113,13 +113,14 @@ def to_transfer_type( @dataclasses.dataclass(slots=True) -class CreateStreamResult: +class CreateStreamResponse: stream: StreamRef | None = None + created: bool | None = None -class _CreateStreamResultTransferTypeConverter( +class _CreateStreamResponseTransferTypeConverter( temporalio.converter.TransferTypeConverter[ - CreateStreamResult, streamsample.v1.stream_service_pb2.CreateStreamResponse + CreateStreamResponse, streamsample.v1.stream_service_pb2.CreateStreamResponse ] ): transfer_type: ( @@ -130,32 +131,35 @@ class _CreateStreamResultTransferTypeConverter( def from_transfer_type( self, value: streamsample.v1.stream_service_pb2.CreateStreamResponse, - type_hint: type[CreateStreamResult], - ) -> CreateStreamResult: - return CreateStreamResult( + type_hint: type[CreateStreamResponse], + ) -> CreateStreamResponse: + return CreateStreamResponse( stream=_StreamRefTransferTypeConverter().from_transfer_type( value.stream, StreamRef ) if value.HasField("stream") else None, + created=value.created if bool(value.created) else None, ) @typing_extensions.override def to_transfer_type( self, - value: CreateStreamResult, + value: CreateStreamResponse, ) -> streamsample.v1.stream_service_pb2.CreateStreamResponse: message = streamsample.v1.stream_service_pb2.CreateStreamResponse() if value.stream is not None: message.stream.CopyFrom( _StreamRefTransferTypeConverter().to_transfer_type(value.stream) ) + if value.created is not None: + message.created = value.created return message temporalio.converter.transfer_type_convertible( - _CreateStreamResultTransferTypeConverter -)(CreateStreamResult) # pyright: ignore[reportUnusedCallResult] + _CreateStreamResponseTransferTypeConverter +)(CreateStreamResponse) # pyright: ignore[reportUnusedCallResult] @dataclasses.dataclass(slots=True) diff --git a/advanced/samples/python/wit/stream_service/operations/create_stream.py b/advanced/samples/python/wit/stream_service/operations/create_stream.py index 842fecf70..4c7630fd2 100644 --- a/advanced/samples/python/wit/stream_service/operations/create_stream.py +++ b/advanced/samples/python/wit/stream_service/operations/create_stream.py @@ -7,15 +7,18 @@ from ..models import ( CreateStreamRequest, - CreateStreamResult, + CreateStreamResponse, StreamRef, ) -from .._resources import Stream +from .._resources import ( + CreateStreamResult, + Stream, +) async def _create_stream( request: CreateStreamRequest, -) -> Stream: +) -> CreateStreamResult: nexus_client = temporalio.workflow.create_nexus_client( service="streamsample.v1.StreamService", endpoint="streams", @@ -23,13 +26,16 @@ async def _create_stream( handle = await nexus_client.start_operation( operation="CreateStream", input=request, - output_type=CreateStreamResult, + output_type=CreateStreamResponse, ) result = await handle if result.stream is None: raise RuntimeError("CreateStream answered without `stream`") - return Stream( - stream=result.stream, + return CreateStreamResult( + stream=Stream( + stream=result.stream, + ), + created=result.created, ) @@ -37,7 +43,7 @@ async def create_stream( *, ref: StreamRef | None = None, retention: datetime.timedelta | None = None, -) -> Stream: +) -> CreateStreamResult: request = CreateStreamRequest( ref=ref, retention=retention, diff --git a/advanced/samples/python/wit/stream_service/services.py b/advanced/samples/python/wit/stream_service/services.py index 4b76e9d3f..3ad9eadb2 100644 --- a/advanced/samples/python/wit/stream_service/services.py +++ b/advanced/samples/python/wit/stream_service/services.py @@ -9,7 +9,7 @@ CloseStreamRequest, CloseStreamResponse, CreateStreamRequest, - CreateStreamResult, + CreateStreamResponse, DescribeStreamRequest, DescribeStreamResponse, ReadRecordsRequest, @@ -21,7 +21,7 @@ class StreamService: create_stream: Operation[ CreateStreamRequest, - CreateStreamResult, + CreateStreamResponse, ] = Operation(name="CreateStream") append_records: Operation[ diff --git a/advanced/samples/python/wit/workflow_run/__init__.py b/advanced/samples/python/wit/workflow_run/__init__.py new file mode 100644 index 000000000..6b5261274 --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/__init__.py @@ -0,0 +1,57 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations + +import collections.abc +import typing + +import nexusrpc +import temporalio.converter +from . import services as _services +from .operations.start_run import start_run +from ._resources.workflow_run import cancel_run + +__all__ = [ + "cancel_run", + "start_run", +] + + +_InputT = typing.TypeVar("_InputT") +_OutputT = typing.TypeVar("_OutputT") + + +_SerializationContextFactory = collections.abc.Callable[ + [_InputT], temporalio.converter.SerializationContext +] + + +class _NexusOperationInfo(typing.Generic[_InputT, _OutputT]): + def __init__( + self, + *, + operation: nexusrpc.Operation[_InputT, _OutputT], + serialization_context: _SerializationContextFactory[_InputT] | None = None, + ) -> None: + self.operation: nexusrpc.Operation[_InputT, _OutputT] = operation + self.serialization_context: _SerializationContextFactory[_InputT] | None = ( + serialization_context + ) + + +__nexus_operation_registry__: dict[ + tuple[str, str], _NexusOperationInfo[typing.Any, typing.Any] +] = { + ( + "temporal.example.v1.WorkflowRunService", + "StartRun", + ): _NexusOperationInfo( + operation=_services.WorkflowRunService.start_run, + ), + ( + "temporal.example.v1.WorkflowRunService", + "CancelRun", + ): _NexusOperationInfo( + operation=_services.WorkflowRunService.cancel_run, + ), +} diff --git a/advanced/samples/python/wit/workflow_run/_resources/__init__.py b/advanced/samples/python/wit/workflow_run/_resources/__init__.py new file mode 100644 index 000000000..c198b2218 --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/_resources/__init__.py @@ -0,0 +1,13 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations + +from .workflow_run import ( + WorkflowRun, + StartRunResult, +) + +__all__ = [ + "WorkflowRun", + "StartRunResult", +] diff --git a/advanced/samples/python/wit/workflow_run/_resources/workflow_run.py b/advanced/samples/python/wit/workflow_run/_resources/workflow_run.py new file mode 100644 index 000000000..cff895a2f --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/_resources/workflow_run.py @@ -0,0 +1,63 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations + +import dataclasses +import temporalio.workflow + +from ..models import ( + CancelRunRequest, + CancelRunResponse, + WorkflowExecution, +) + + +@dataclasses.dataclass +class WorkflowRun: + workflow_id: str + run_id: str | None + + async def cancel( + self, + reason: str | None = None, + ) -> None: + request = CancelRunRequest( + workflow_execution=WorkflowExecution( + workflow_id=self.workflow_id, run_id=self.run_id + ), + reason=reason, + ) + handle = await _cancel_run(request) + await handle + + +@dataclasses.dataclass(slots=True) +class StartRunResult: + run: WorkflowRun + started: bool | None = None + + +async def _cancel_run( + request: CancelRunRequest, +) -> temporalio.workflow.NexusOperationHandle[CancelRunResponse,]: + nexus_client = temporalio.workflow.create_nexus_client( + service="temporal.example.v1.WorkflowRunService", + endpoint="__temporal_system", + ) + return await nexus_client.start_operation( + operation="CancelRun", + input=request, + output_type=CancelRunResponse, + ) + + +async def cancel_run( + *, + workflow_execution: WorkflowExecution, + reason: str | None = None, +) -> temporalio.workflow.NexusOperationHandle[CancelRunResponse,]: + request = CancelRunRequest( + workflow_execution=workflow_execution, + reason=reason, + ) + return await _cancel_run(request) diff --git a/advanced/samples/python/wit/workflow_run/_system_nexus_interceptor.py b/advanced/samples/python/wit/workflow_run/_system_nexus_interceptor.py new file mode 100644 index 000000000..9829e2626 --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/_system_nexus_interceptor.py @@ -0,0 +1,142 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations + + +import abc +import typing + +from . import models + +if typing.TYPE_CHECKING: + import temporalio.workflow + from temporalio.worker._interceptor import StartNexusOperationInput + + +__all__ = [ + "_start_system_nexus_operation", + "_SystemNexusWorkflowOutboundInterceptorBase", + "_SystemNexusWorkflowOutboundInterceptorTerminal", +] + + +_InputT = typing.TypeVar("_InputT") +_OutputT = typing.TypeVar("_OutputT") + + +async def _start_system_nexus_operation( + interceptor: _SystemNexusWorkflowOutboundInterceptorBase, + input: StartNexusOperationInput[_InputT, _OutputT], +) -> temporalio.workflow.NexusOperationHandle[_OutputT]: + if ( + input.service == "temporal.example.v1.WorkflowRunService" + and input.operation_name == "StartRun" + ): + typed_input = typing.cast( + "StartNexusOperationInput[models.StartRunRequest, models.StartWorkflowExecutionResponse]", + input, + ) + # The dispatch check above establishes that this operation's response type is _OutputT. + return typing.cast( + "temporalio.workflow.NexusOperationHandle[_OutputT]", + await interceptor.start_start_run(typed_input.input), + ) + if ( + input.service == "temporal.example.v1.WorkflowRunService" + and input.operation_name == "CancelRun" + ): + typed_input = typing.cast( + "StartNexusOperationInput[models.CancelRunRequest, models.CancelRunResponse]", + input, + ) + # The dispatch check above establishes that this operation's response type is _OutputT. + return typing.cast( + "temporalio.workflow.NexusOperationHandle[_OutputT]", + await interceptor.start_cancel_run(typed_input.input), + ) + return await interceptor.start_system_nexus_operation(input) + + +class _SystemNexusWorkflowOutboundInterceptorBase(abc.ABC): + @abc.abstractmethod + def _next_system_nexus_interceptor( + self, + ) -> _SystemNexusWorkflowOutboundInterceptorBase: ... + + @abc.abstractmethod + async def start_system_nexus_operation( + self, + input: StartNexusOperationInput[_InputT, _OutputT], + ) -> temporalio.workflow.NexusOperationHandle[_OutputT]: + """Intercept a System Nexus operation through the generic hook.""" + ... + + async def start_start_run( + self, request: models.StartRunRequest + ) -> temporalio.workflow.NexusOperationHandle[ + models.StartWorkflowExecutionResponse + ]: + """Intercept the StartRun operation.""" + return await self._next_system_nexus_interceptor().start_start_run(request) + + async def start_cancel_run( + self, request: models.CancelRunRequest + ) -> temporalio.workflow.NexusOperationHandle[models.CancelRunResponse]: + """Intercept the CancelRun operation.""" + return await self._next_system_nexus_interceptor().start_cancel_run(request) + + +class _SystemNexusWorkflowOutboundInterceptorTerminal(abc.ABC): + @abc.abstractmethod + async def _intercept_system_nexus_operation( + self, + input: StartNexusOperationInput[_InputT, _OutputT], + ) -> temporalio.workflow.NexusOperationHandle[_OutputT]: ... + + async def start_start_run( + self, request: models.StartRunRequest + ) -> temporalio.workflow.NexusOperationHandle[ + models.StartWorkflowExecutionResponse + ]: + from temporalio.worker._interceptor import StartNexusOperationInput + from temporalio.nexus.system import TEMPORAL_SYSTEM_ENDPOINT + from temporalio.workflow import NexusOperationCancellationType + + return await self._intercept_system_nexus_operation( + StartNexusOperationInput( + endpoint=TEMPORAL_SYSTEM_ENDPOINT, + service="temporal.example.v1.WorkflowRunService", + operation="StartRun", + input=request, + output_type=models.StartWorkflowExecutionResponse, + schedule_to_close_timeout=None, + schedule_to_start_timeout=None, + start_to_close_timeout=None, + cancellation_type=NexusOperationCancellationType.WAIT_COMPLETED, + headers=None, + summary=None, + ) + ) + + async def start_cancel_run( + self, request: models.CancelRunRequest + ) -> temporalio.workflow.NexusOperationHandle[models.CancelRunResponse]: + from temporalio.worker._interceptor import StartNexusOperationInput + from temporalio.nexus.system import TEMPORAL_SYSTEM_ENDPOINT + from temporalio.workflow import NexusOperationCancellationType + + return await self._intercept_system_nexus_operation( + StartNexusOperationInput( + endpoint=TEMPORAL_SYSTEM_ENDPOINT, + service="temporal.example.v1.WorkflowRunService", + operation="CancelRun", + input=request, + output_type=models.CancelRunResponse, + schedule_to_close_timeout=None, + schedule_to_start_timeout=None, + start_to_close_timeout=None, + cancellation_type=NexusOperationCancellationType.WAIT_COMPLETED, + headers=None, + summary=None, + ) + ) diff --git a/advanced/samples/python/wit/workflow_run/models.py b/advanced/samples/python/wit/workflow_run/models.py new file mode 100644 index 000000000..872af9e6c --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/models.py @@ -0,0 +1,280 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations + +import dataclasses +import typing_extensions +import datetime +import temporalio.api.common.v1.message_pb2 +import temporalio.api.workflowservice.v1.request_response_pb2 +import temporalio.converter + +import temporal_support as _support + + +@dataclasses.dataclass(slots=True) +class StartRunRequest: + workflow: str + workflow_id: str + task_queue: str + workflow_start_delay: datetime.timedelta | None = None + namespace: str = dataclasses.field(default_factory=_support.workflow_namespace) + + +class _StartRunRequestTransferTypeConverter( + temporalio.converter.TransferTypeConverter[ + StartRunRequest, + temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest, + ] +): + transfer_type: ( + type[ + temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest + ] + | None + ) = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest + + @typing_extensions.override + def from_transfer_type( + self, + value: temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest, + type_hint: type[StartRunRequest], + ) -> StartRunRequest: + if not value.HasField("workflow_type"): + raise ValueError("missing required field StartRunRequest.workflow") + workflow = _support.workflow_type_from_proto(value.workflow_type) + if not value.workflow_id: + raise ValueError("missing required field StartRunRequest.workflow_id") + workflow_id = value.workflow_id + if not value.HasField("task_queue"): + raise ValueError("missing required field StartRunRequest.task_queue") + task_queue = _support.task_queue_from_proto(value.task_queue) + return StartRunRequest( + workflow=workflow, + workflow_id=workflow_id, + task_queue=task_queue, + workflow_start_delay=_support.duration_from_proto( + value.workflow_start_delay + ) + if value.HasField("workflow_start_delay") + else None, + namespace=value.namespace, + ) + + @typing_extensions.override + def to_transfer_type( + self, + value: StartRunRequest, + ) -> temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest: + message = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest() + message.workflow_type.CopyFrom(_support.workflow_type_to_proto(value.workflow)) + message.workflow_id = value.workflow_id + message.task_queue.CopyFrom(_support.task_queue_to_proto(value.task_queue)) + if value.workflow_start_delay is not None: + message.workflow_start_delay.CopyFrom( + _support.duration_to_proto(value.workflow_start_delay) + ) + message.namespace = value.namespace + return message + + +temporalio.converter.transfer_type_convertible(_StartRunRequestTransferTypeConverter)( + StartRunRequest +) # pyright: ignore[reportUnusedCallResult] + + +@dataclasses.dataclass(slots=True) +class StartWorkflowExecutionResponse: + run_id: str | None = None + started: bool | None = None + + +class _StartWorkflowExecutionResponseTransferTypeConverter( + temporalio.converter.TransferTypeConverter[ + StartWorkflowExecutionResponse, + temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse, + ] +): + transfer_type: ( + type[ + temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse + ] + | None + ) = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse + + @typing_extensions.override + def from_transfer_type( + self, + value: temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse, + type_hint: type[StartWorkflowExecutionResponse], + ) -> StartWorkflowExecutionResponse: + return StartWorkflowExecutionResponse( + run_id=value.run_id if bool(value.run_id) else None, + started=value.started if bool(value.started) else None, + ) + + @typing_extensions.override + def to_transfer_type( + self, + value: StartWorkflowExecutionResponse, + ) -> temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse: + message = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse() + if value.run_id is not None: + message.run_id = value.run_id + if value.started is not None: + message.started = value.started + return message + + +temporalio.converter.transfer_type_convertible( + _StartWorkflowExecutionResponseTransferTypeConverter +)(StartWorkflowExecutionResponse) # pyright: ignore[reportUnusedCallResult] + + +@dataclasses.dataclass(slots=True, kw_only=True) +class CancelRunRequest: + namespace: str = dataclasses.field(default_factory=_support.workflow_namespace) + workflow_execution: WorkflowExecution + reason: str | None = None + + +class _CancelRunRequestTransferTypeConverter( + temporalio.converter.TransferTypeConverter[ + CancelRunRequest, + temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest, + ] +): + transfer_type: ( + type[ + temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest + ] + | None + ) = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest + + @typing_extensions.override + def from_transfer_type( + self, + value: temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest, + type_hint: type[CancelRunRequest], + ) -> CancelRunRequest: + if not value.HasField("workflow_execution"): + raise ValueError( + "missing required field CancelRunRequest.workflow_execution" + ) + workflow_execution = ( + _WorkflowExecutionTransferTypeConverter().from_transfer_type( + value.workflow_execution, WorkflowExecution + ) + ) + return CancelRunRequest( + namespace=value.namespace, + workflow_execution=workflow_execution, + reason=value.reason if bool(value.reason) else None, + ) + + @typing_extensions.override + def to_transfer_type( + self, + value: CancelRunRequest, + ) -> temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest: + message = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest() + message.namespace = value.namespace + message.workflow_execution.CopyFrom( + _WorkflowExecutionTransferTypeConverter().to_transfer_type( + value.workflow_execution + ) + ) + if value.reason is not None: + message.reason = value.reason + return message + + +temporalio.converter.transfer_type_convertible(_CancelRunRequestTransferTypeConverter)( + CancelRunRequest +) # pyright: ignore[reportUnusedCallResult] + + +@dataclasses.dataclass(slots=True) +class WorkflowExecution: + workflow_id: str + run_id: str | None = None + + +class _WorkflowExecutionTransferTypeConverter( + temporalio.converter.TransferTypeConverter[ + WorkflowExecution, temporalio.api.common.v1.message_pb2.WorkflowExecution + ] +): + transfer_type: ( + type[temporalio.api.common.v1.message_pb2.WorkflowExecution] | None + ) = temporalio.api.common.v1.message_pb2.WorkflowExecution + + @typing_extensions.override + def from_transfer_type( + self, + value: temporalio.api.common.v1.message_pb2.WorkflowExecution, + type_hint: type[WorkflowExecution], + ) -> WorkflowExecution: + if not value.workflow_id: + raise ValueError("missing required field WorkflowExecution.workflow_id") + workflow_id = value.workflow_id + return WorkflowExecution( + workflow_id=workflow_id, + run_id=value.run_id if bool(value.run_id) else None, + ) + + @typing_extensions.override + def to_transfer_type( + self, + value: WorkflowExecution, + ) -> temporalio.api.common.v1.message_pb2.WorkflowExecution: + message = temporalio.api.common.v1.message_pb2.WorkflowExecution() + message.workflow_id = value.workflow_id + if value.run_id is not None: + message.run_id = value.run_id + return message + + +temporalio.converter.transfer_type_convertible(_WorkflowExecutionTransferTypeConverter)( + WorkflowExecution +) # pyright: ignore[reportUnusedCallResult] + + +@dataclasses.dataclass(slots=True) +class CancelRunResponse: + pass + + +class _CancelRunResponseTransferTypeConverter( + temporalio.converter.TransferTypeConverter[ + CancelRunResponse, + temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse, + ] +): + transfer_type: ( + type[ + temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse + ] + | None + ) = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse + + @typing_extensions.override + def from_transfer_type( + self, + value: temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse, + type_hint: type[CancelRunResponse], + ) -> CancelRunResponse: + return CancelRunResponse() + + @typing_extensions.override + def to_transfer_type( + self, + value: CancelRunResponse, + ) -> temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse: + message = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse() + return message + + +temporalio.converter.transfer_type_convertible(_CancelRunResponseTransferTypeConverter)( + CancelRunResponse +) # pyright: ignore[reportUnusedCallResult] diff --git a/advanced/samples/python/wit/workflow_run/operations/__init__.py b/advanced/samples/python/wit/workflow_run/operations/__init__.py new file mode 100644 index 000000000..ca23418da --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/operations/__init__.py @@ -0,0 +1,3 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations diff --git a/advanced/samples/python/wit/workflow_run/operations/start_run.py b/advanced/samples/python/wit/workflow_run/operations/start_run.py new file mode 100644 index 000000000..50be20fcd --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/operations/start_run.py @@ -0,0 +1,53 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations + +import datetime +import temporalio.workflow + +from ..models import ( + StartRunRequest, + StartWorkflowExecutionResponse, +) +from .._resources import ( + StartRunResult, + WorkflowRun, +) + + +async def _start_run( + request: StartRunRequest, +) -> StartRunResult: + nexus_client = temporalio.workflow.create_nexus_client( + service="temporal.example.v1.WorkflowRunService", + endpoint="__temporal_system", + ) + handle = await nexus_client.start_operation( + operation="StartRun", + input=request, + output_type=StartWorkflowExecutionResponse, + ) + result = await handle + return StartRunResult( + run=WorkflowRun( + workflow_id=request.workflow_id, + run_id=result.run_id or None, + ), + started=result.started, + ) + + +async def start_run( + *, + workflow: str, + workflow_id: str, + task_queue: str, + workflow_start_delay: datetime.timedelta | None = None, +) -> StartRunResult: + request = StartRunRequest( + workflow=workflow, + workflow_id=workflow_id, + task_queue=task_queue, + workflow_start_delay=workflow_start_delay, + ) + return await _start_run(request) diff --git a/advanced/samples/python/wit/workflow_run/services.py b/advanced/samples/python/wit/workflow_run/services.py new file mode 100644 index 000000000..4d6048dc2 --- /dev/null +++ b/advanced/samples/python/wit/workflow_run/services.py @@ -0,0 +1,24 @@ +# Generated by nexgen latest. DO NOT EDIT! + +from __future__ import annotations + +from nexusrpc import Operation, service +from .models import ( + CancelRunRequest, + CancelRunResponse, + StartRunRequest, + StartWorkflowExecutionResponse, +) + + +@service(name="temporal.example.v1.WorkflowRunService") +class WorkflowRunService: + start_run: Operation[ + StartRunRequest, + StartWorkflowExecutionResponse, + ] = Operation(name="StartRun") + + cancel_run: Operation[ + CancelRunRequest, + CancelRunResponse, + ] = Operation(name="CancelRun") diff --git a/advanced/samples/typescript/wit/stream-service/models.ts b/advanced/samples/typescript/wit/stream-service/models.ts index 947cd35e9..c7736db1f 100644 --- a/advanced/samples/typescript/wit/stream-service/models.ts +++ b/advanced/samples/typescript/wit/stream-service/models.ts @@ -4,6 +4,7 @@ import type { streamsample } from "#streamsample"; import type * as common from "@temporalio/common"; import type Long from "long"; import * as support from "../../support/temporal_model_converters"; +import type { Stream } from "./resources"; function requiredField( value: T | null | undefined, @@ -16,33 +17,36 @@ function requiredField( return value; } -export interface CreateStreamResult { +export interface CreateStreamResponse { stream?: StreamRef; + created?: boolean; } -function createStreamResultFromProto( +function createStreamResponseFromProto( proto: streamsample.v1.ICreateStreamResponse, -): CreateStreamResult { +): CreateStreamResponse { return { stream: proto.stream == null ? undefined : streamRefFromProto(proto.stream), + created: proto.created ?? undefined, }; } -function createStreamResultToProto( - model: CreateStreamResult, +function createStreamResponseToProto( + model: CreateStreamResponse, ): streamsample.v1.ICreateStreamResponse { return { stream: model.stream == null ? undefined : streamRefToProto(model.stream), + created: model.created, }; } -export const createStreamResultTransferTypeConverter = { - fromTransferType(value: streamsample.v1.ICreateStreamResponse): CreateStreamResult { - return createStreamResultFromProto(value); +export const createStreamResponseTransferTypeConverter = { + fromTransferType(value: streamsample.v1.ICreateStreamResponse): CreateStreamResponse { + return createStreamResponseFromProto(value); }, - toTransferType(value: CreateStreamResult): streamsample.v1.ICreateStreamResponse { - return createStreamResultToProto(value); + toTransferType(value: CreateStreamResponse): streamsample.v1.ICreateStreamResponse { + return createStreamResponseToProto(value); }, }; export interface StreamRef { @@ -443,3 +447,7 @@ export const describeStreamRequestTransferTypeConverter = { return describeStreamRequestToProto(value); }, }; +export interface CreateStreamResult { + stream: Stream; + created?: boolean; +} diff --git a/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts b/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts index 1ffdca0db..2229b2b43 100644 --- a/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts +++ b/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts @@ -2,7 +2,7 @@ import * as workflow from "@temporalio/workflow"; import { streamService } from "../services"; -import type { CreateStreamRequest } from "../models"; +import type { CreateStreamRequest, CreateStreamResult } from "../models"; import { Stream } from "../resources"; function requiredField( @@ -19,7 +19,9 @@ function requiredField( /** * @param request - Request for the operation. */ -export async function createStream(request: CreateStreamRequest): Promise { +export async function createStream( + request: CreateStreamRequest, +): Promise { const client = workflow.createNexusServiceClient({ service: streamService, endpoint: "streams", @@ -29,5 +31,8 @@ export async function createStream(request: CreateStreamRequest): Promise({ + createStream: nexus.operation({ name: "CreateStream", inputType: { transferTypeConverter: createStreamRequestTransferTypeConverter }, - outputType: { transferTypeConverter: createStreamResultTransferTypeConverter }, + outputType: { transferTypeConverter: createStreamResponseTransferTypeConverter }, }), appendRecords: nexus.operation({ name: "AppendRecords", diff --git a/advanced/samples/typescript/wit/workflow-run/index.ts b/advanced/samples/typescript/wit/workflow-run/index.ts new file mode 100644 index 000000000..f3eac7411 --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/index.ts @@ -0,0 +1,10 @@ +// Generated by nexgen latest. DO NOT EDIT! + +export { startRun } from "./operations/start-run"; +export { cancelRun } from "./operations/cancel-run"; +export type { + CancelRunRequest, + CancelRunResponse, + StartRunRequest, + StartWorkflowExecutionResponse, +} from "./models"; diff --git a/advanced/samples/typescript/wit/workflow-run/interceptors.ts b/advanced/samples/typescript/wit/workflow-run/interceptors.ts new file mode 100644 index 000000000..c4c361b75 --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/interceptors.ts @@ -0,0 +1,52 @@ +// Generated by nexgen latest. DO NOT EDIT! + +import { composeInterceptors } from "../../../interceptor-composition"; +import type * as nexus from "../../../nexus"; +import type { + StartWorkflowExecutionResponse, + StartRunRequest, + CancelRunResponse, + CancelRunRequest, +} from "./models"; + +export interface SystemNexusWorkflowOutboundCallsInterceptor { + startRun?: ( + input: StartRunRequest, + next: ( + input: StartRunRequest, + ) => Promise>, + ) => Promise>; + cancelRun?: ( + input: CancelRunRequest, + next: ( + input: CancelRunRequest, + ) => Promise>, + ) => Promise>; +} + +/** Dispatches the operation-specific interceptor chain selected by service and operation. */ +export function dispatchSystemNexusSpecificInterceptors( + service: string, + operation: string, + interceptors: SystemNexusWorkflowOutboundCallsInterceptor[], + input: unknown, + next: (input: Input) => Promise>, +): Promise> { + switch (service) { + case "temporal.example.v1.WorkflowRunService": + switch (operation) { + case "StartRun": + return composeInterceptors(interceptors, "startRun", (request) => + next(request), + )(input as StartRunRequest) as Promise>; + case "CancelRun": + return composeInterceptors(interceptors, "cancelRun", (request) => + next(request), + )(input as CancelRunRequest) as Promise>; + default: + return next(input); + } + default: + return next(input); + } +} diff --git a/advanced/samples/typescript/wit/workflow-run/models.ts b/advanced/samples/typescript/wit/workflow-run/models.ts new file mode 100644 index 000000000..7b2ed31ea --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/models.ts @@ -0,0 +1,245 @@ +// Generated by nexgen latest. DO NOT EDIT! + +import type * as common from "@temporalio/common"; +import type { temporal } from "@temporalio/proto"; +import * as support from "../../support/temporal_model_converters"; +import type { WorkflowRun } from "./resources"; + +function requiredField( + value: T | null | undefined, + owner: string, + field: string, +): T { + if (value == null) { + throw new Error(`missing required field ${owner}.${field}`); + } + return value; +} + +export interface StartWorkflowExecutionResponse { + runId?: string; + started?: boolean; +} + +function startWorkflowExecutionResponseFromProto( + proto: temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse, +): StartWorkflowExecutionResponse { + return { + runId: proto.runId ?? undefined, + started: proto.started ?? undefined, + }; +} + +function startWorkflowExecutionResponseToProto( + model: StartWorkflowExecutionResponse, +): temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse { + return { + runId: model.runId, + started: model.started, + }; +} + +export const startWorkflowExecutionResponseTransferTypeConverter = { + fromTransferType( + value: temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse, + ): StartWorkflowExecutionResponse { + return startWorkflowExecutionResponseFromProto(value); + }, + + toTransferType( + value: StartWorkflowExecutionResponse, + ): temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse { + return startWorkflowExecutionResponseToProto(value); + }, +}; +export interface StartRunRequest { + workflow: string; + workflowId: string; + taskQueue: string; + workflowStartDelay?: common.Duration; + namespace: string; +} + +function startRunRequestFromProto( + proto: temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest, +): StartRunRequest { + return { + workflow: requiredField( + support.workflowTypeFromProto( + requiredField(proto.workflowType, "StartRunRequest", "workflow"), + ), + "StartRunRequest", + "workflow", + ), + workflowId: requiredField( + proto.workflowId === "" ? undefined : proto.workflowId, + "StartRunRequest", + "workflowId", + ), + taskQueue: requiredField( + support.taskQueueFromProto( + requiredField(proto.taskQueue, "StartRunRequest", "taskQueue"), + ), + "StartRunRequest", + "taskQueue", + ), + workflowStartDelay: + proto.workflowStartDelay == null + ? undefined + : support.durationFromProto(proto.workflowStartDelay), + namespace: requiredField( + proto.namespace === "" ? undefined : proto.namespace, + "sourced field", + "namespace", + ), + }; +} + +function startRunRequestToProto( + model: StartRunRequest, +): temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest { + return { + workflowType: support.workflowTypeToProto( + requiredField(model.workflow, "StartRunRequest", "workflow"), + ), + workflowId: requiredField(model.workflowId, "StartRunRequest", "workflowId"), + taskQueue: support.taskQueueToProto( + requiredField(model.taskQueue, "StartRunRequest", "taskQueue"), + ), + workflowStartDelay: + model.workflowStartDelay == null + ? undefined + : support.durationToProto(model.workflowStartDelay), + namespace: model.namespace ?? support.workflowNamespace(), + }; +} + +export const startRunRequestTransferTypeConverter = { + fromTransferType( + value: temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest, + ): StartRunRequest { + return startRunRequestFromProto(value); + }, + + toTransferType( + value: StartRunRequest, + ): temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest { + return startRunRequestToProto(value); + }, +}; +export type CancelRunResponse = Record; + +function cancelRunResponseFromProto( + proto: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse, +): CancelRunResponse { + return {}; +} + +function cancelRunResponseToProto( + model: CancelRunResponse, +): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse { + return {}; +} + +export const cancelRunResponseTransferTypeConverter = { + fromTransferType( + value: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse, + ): CancelRunResponse { + return cancelRunResponseFromProto(value); + }, + + toTransferType( + value: CancelRunResponse, + ): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse { + return cancelRunResponseToProto(value); + }, +}; +export interface CancelRunRequest { + workflowExecution: WorkflowExecution; + reason?: string; + namespace: string; +} + +function cancelRunRequestFromProto( + proto: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest, +): CancelRunRequest { + return { + workflowExecution: workflowExecutionFromProto( + requiredField(proto.workflowExecution, "CancelRunRequest", "workflowExecution"), + ), + reason: proto.reason ?? undefined, + namespace: requiredField( + proto.namespace === "" ? undefined : proto.namespace, + "sourced field", + "namespace", + ), + }; +} + +function cancelRunRequestToProto( + model: CancelRunRequest, +): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest { + return { + workflowExecution: workflowExecutionToProto( + requiredField(model.workflowExecution, "CancelRunRequest", "workflowExecution"), + ), + reason: model.reason, + namespace: model.namespace ?? support.workflowNamespace(), + }; +} + +export const cancelRunRequestTransferTypeConverter = { + fromTransferType( + value: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest, + ): CancelRunRequest { + return cancelRunRequestFromProto(value); + }, + + toTransferType( + value: CancelRunRequest, + ): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest { + return cancelRunRequestToProto(value); + }, +}; +export interface WorkflowExecution { + workflowId: string; + runId?: string; +} + +function workflowExecutionFromProto( + proto: temporal.api.common.v1.IWorkflowExecution, +): WorkflowExecution { + return { + workflowId: requiredField( + proto.workflowId === "" ? undefined : proto.workflowId, + "WorkflowExecution", + "workflowId", + ), + runId: proto.runId ?? undefined, + }; +} + +function workflowExecutionToProto( + model: WorkflowExecution, +): temporal.api.common.v1.IWorkflowExecution { + return { + workflowId: requiredField(model.workflowId, "WorkflowExecution", "workflowId"), + runId: model.runId, + }; +} + +export const workflowExecutionTransferTypeConverter = { + fromTransferType( + value: temporal.api.common.v1.IWorkflowExecution, + ): WorkflowExecution { + return workflowExecutionFromProto(value); + }, + + toTransferType(value: WorkflowExecution): temporal.api.common.v1.IWorkflowExecution { + return workflowExecutionToProto(value); + }, +}; +export interface StartRunResult { + run: WorkflowRun; + started?: boolean; +} diff --git a/advanced/samples/typescript/wit/workflow-run/operations/cancel-run.ts b/advanced/samples/typescript/wit/workflow-run/operations/cancel-run.ts new file mode 100644 index 000000000..de014d7bb --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/operations/cancel-run.ts @@ -0,0 +1,29 @@ +// Generated by nexgen latest. DO NOT EDIT! + +import * as nexus from "../../../../nexus"; +import { workflowRunService } from "../services"; +import type { CancelRunResponse, CancelRunRequest } from "../models"; +import * as support from "../../../support/temporal_model_converters"; + +type CancelRunInput = CancelRunRequest extends infer Input + ? Input extends unknown + ? Omit & { namespace?: never } + : never + : never; + +/** + * @param request - Request for the operation. + */ +export async function cancelRun( + requestInput: CancelRunInput, +): Promise> { + const request = { + ...requestInput, + namespace: support.workflowNamespace(), + } as CancelRunRequest; + const client = nexus.createNexusServiceClient({ + service: workflowRunService, + endpoint: "temporal-system", + }); + return await client.startOperation(workflowRunService.operations.cancelRun, request); +} diff --git a/advanced/samples/typescript/wit/workflow-run/operations/start-run.ts b/advanced/samples/typescript/wit/workflow-run/operations/start-run.ts new file mode 100644 index 000000000..661c319b7 --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/operations/start-run.ts @@ -0,0 +1,36 @@ +// Generated by nexgen latest. DO NOT EDIT! + +import * as nexus from "../../../../nexus"; +import { workflowRunService } from "../services"; +import type { StartRunRequest, StartRunResult } from "../models"; +import * as support from "../../../support/temporal_model_converters"; +import { WorkflowRun } from "../resources"; + +type StartRunInput = StartRunRequest extends infer Input + ? Input extends unknown + ? Omit & { namespace?: never } + : never + : never; + +/** + * @param request - Request for the operation. + */ +export async function startRun(requestInput: StartRunInput): Promise { + const request = { + ...requestInput, + namespace: support.workflowNamespace(), + } as StartRunRequest; + const client = nexus.createNexusServiceClient({ + service: workflowRunService, + endpoint: "temporal-system", + }); + const handle = await client.startOperation( + workflowRunService.operations.startRun, + request, + ); + const result = await handle.result(); + return { + run: new WorkflowRun(request.workflowId, result.runId ?? undefined), + started: result.started, + }; +} diff --git a/advanced/samples/typescript/wit/workflow-run/registry.ts b/advanced/samples/typescript/wit/workflow-run/registry.ts new file mode 100644 index 000000000..135000ef6 --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/registry.ts @@ -0,0 +1,12 @@ +// Generated by nexgen latest. DO NOT EDIT! + +export const operationRegistry = [ + { + service: "temporal.example.v1.WorkflowRunService", + operation: "StartRun", + }, + { + service: "temporal.example.v1.WorkflowRunService", + operation: "CancelRun", + }, +] as const; diff --git a/advanced/samples/typescript/wit/workflow-run/resources.ts b/advanced/samples/typescript/wit/workflow-run/resources.ts new file mode 100644 index 000000000..9ccdd3496 --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/resources.ts @@ -0,0 +1,19 @@ +// Generated by nexgen latest. DO NOT EDIT! + +import { cancelRun } from "./operations/cancel-run"; + +export class WorkflowRun { + public constructor( + public readonly workflowId: string, + public readonly runId: string | undefined, + ) {} + + public async cancel(reason?: string): Promise { + const request = { + workflowExecution: { workflowId: this.workflowId, runId: this.runId }, + reason: reason, + }; + const handle = await cancelRun(request); + await handle.result(); + } +} diff --git a/advanced/samples/typescript/wit/workflow-run/services.ts b/advanced/samples/typescript/wit/workflow-run/services.ts new file mode 100644 index 000000000..aa9bd4494 --- /dev/null +++ b/advanced/samples/typescript/wit/workflow-run/services.ts @@ -0,0 +1,33 @@ +// Generated by nexgen latest. DO NOT EDIT! + +import * as nexus from "nexus-rpc"; +import { + startWorkflowExecutionResponseTransferTypeConverter, + startRunRequestTransferTypeConverter, + cancelRunResponseTransferTypeConverter, + cancelRunRequestTransferTypeConverter, +} from "./models"; +import type { + StartWorkflowExecutionResponse, + StartRunRequest, + CancelRunResponse, + CancelRunRequest, +} from "./models"; + +export const workflowRunService = nexus.service( + "temporal.example.v1.WorkflowRunService", + { + startRun: nexus.operation({ + name: "StartRun", + inputType: { transferTypeConverter: startRunRequestTransferTypeConverter }, + outputType: { + transferTypeConverter: startWorkflowExecutionResponseTransferTypeConverter, + }, + }), + cancelRun: nexus.operation({ + name: "CancelRun", + inputType: { transferTypeConverter: cancelRunRequestTransferTypeConverter }, + outputType: { transferTypeConverter: cancelRunResponseTransferTypeConverter }, + }), + }, +); diff --git a/changelog/added/holding-heron.md b/changelog/added/holding-heron.md new file mode 100644 index 000000000..5fb767524 --- /dev/null +++ b/changelog/added/holding-heron.md @@ -0,0 +1 @@ +An operation can return a proto-backed record that holds a resource in one `own` field, in Python, TypeScript, .NET and Go. The resource binds from the record's message, the other fields come from the same message, and the message travels as a wire record with the same name. diff --git a/src/generator/dotnet.rs b/src/generator/dotnet.rs index d382268f5..939bd47fa 100644 --- a/src/generator/dotnet.rs +++ b/src/generator/dotnet.rs @@ -15,7 +15,8 @@ use crate::planning::{ PlannedSpec, PlannedType, operation_input_model, }; use crate::planning::{ - RequestPlan, RequestPlanField, RequestPlanSource, ResolvedResourceBindingSource, + PlannedResourceHolderField, RequestPlan, RequestPlanField, RequestPlanSource, + ResolvedResourceBindingSource, }; use crate::spec::{ AliasTypeSpec, EnumSpec, ExternalTypeSourceSpec, ExternalTypeSpec, FlagsSpec, @@ -904,11 +905,14 @@ impl<'a> ApiPlanner<'a> { let expression = operation_transform_expression(operation, "request", "result", self.api_plan); output.push_str(" return "); - if let Some(resource) = &operation.data.output_resource_return { - output.push_str(&csharp_type_name(&resource.resource_type_name)); - output.push_str(".BindClient("); - output.push_str(&expression); - output.push_str(", _client)"); + if operation.data.output_resource_return.is_some() { + output.push_str(&resource_return_expression( + operation, + "request", + "result", + self.api_plan, + Some("_client"), + )); } else { output.push_str(&expression); } @@ -1108,11 +1112,14 @@ impl<'a> ApiPlanner<'a> { let expression = operation_transform_expression(operation, "request", "result", self.api_plan); output.push_str(" return "); - if endpoint_parameter && let Some(resource) = &operation.data.output_resource_return { - output.push_str(&csharp_type_name(&resource.resource_type_name)); - output.push_str(".BindClient("); - output.push_str(&expression); - output.push_str(", client)"); + if endpoint_parameter && operation.data.output_resource_return.is_some() { + output.push_str(&resource_return_expression( + operation, + "request", + "result", + self.api_plan, + Some("client"), + )); } else { output.push_str(&expression); } @@ -1196,11 +1203,14 @@ impl<'a> ApiPlanner<'a> { let expression = operation_transform_expression(operation, "request", "result", self.api_plan); output.push_str(" return "); - if let Some(resource) = &operation.data.output_resource_return { - output.push_str(&csharp_type_name(&resource.resource_type_name)); - output.push_str(".BindClient("); - output.push_str(&expression); - output.push_str(", RequireNexgenClient())"); + if operation.data.output_resource_return.is_some() { + output.push_str(&resource_return_expression( + operation, + "request", + "result", + self.api_plan, + Some("RequireNexgenClient()"), + )); } else { output.push_str(&expression); } @@ -1306,7 +1316,12 @@ impl<'a> ApiPlanner<'a> { return type_name.to_string(); } if let Some(resource) = &operation.data.output_resource_return { - return csharp_type_name(&resource.resource_type_name); + return csharp_type_name( + resource + .holder + .as_ref() + .map_or(&resource.resource_type_name, |holder| &holder.model_name), + ); } self.operation_output_type(operation) } @@ -3642,42 +3657,93 @@ fn operation_transform_expression( .replace("request", request_expr) .replace("result", result_expr); } - if let Some(resource) = &operation.data.output_resource_return { - let args = resource - .bindings - .iter() - .map(|binding| { - let expr = resource_return_binding_expr( - resource, - binding, - request_expr, - result_expr, - api_plan, - ); - // A response model field is always nullable, so a required one is checked. - match &binding.source { - ResolvedResourceBindingSource::ResultField { - proto_field_name, .. - } if !binding.optional => format!( - "({expr} ?? throw new System.InvalidOperationException({}))", - csharp_string_literal(&format!( - "{} answered without `{proto_field_name}`", - operation.wire_name - )) - ), - _ => expr, - } - }) - .collect::>(); - return format!( - "new {}({})", - csharp_type_name(&resource.resource_type_name), - args.join(", ") - ); + if operation.data.output_resource_return.is_some() { + return resource_return_expression(operation, request_expr, result_expr, api_plan, None); } result_expr.to_string() } +/// The resource that an operation returns, bound to `client` when one is given. A result +/// record that holds the resource gets it in its resource field, and each other field from +/// the field of the same name in the wire record. +fn resource_return_expression( + operation: &PlannedOperation, + request_expr: &str, + result_expr: &str, + api_plan: &PlannedSpec, + client: Option<&str>, +) -> String { + let resource = operation + .data + .output_resource_return + .as_ref() + .expect("the operation returns a resource"); + let type_name = csharp_type_name(&resource.resource_type_name); + let args = resource + .bindings + .iter() + .map(|binding| { + let expr = resource_return_binding_expr( + resource, + binding, + request_expr, + result_expr, + api_plan, + ); + // A response model field is always nullable, so a required one is checked. + match &binding.source { + ResolvedResourceBindingSource::ResultField { + proto_field_name, .. + } if !binding.optional => format!( + "({expr} ?? throw new System.InvalidOperationException({}))", + csharp_string_literal(&format!( + "{} answered without `{proto_field_name}`", + operation.wire_name + )) + ), + _ => expr, + } + }) + .collect::>(); + let mut value = format!("new {type_name}({})", args.join(", ")); + if let Some(client) = client { + value = format!("{type_name}.BindClient({value}, {client})"); + } + let Some(holder) = &resource.holder else { + return value; + }; + let field_value = |field: &PlannedResourceHolderField| { + if field.resource { + value.clone() + } else { + format!("{result_expr}.{}", csharp_type_name(&field.name)) + } + }; + let required = holder + .fields + .iter() + .filter(|field| field.required) + .map(field_value) + .collect::>(); + let optional = holder + .fields + .iter() + .filter(|field| !field.required) + .map(|field| format!("{} = {}", csharp_type_name(&field.name), field_value(field))) + .collect::>(); + let mut expression = format!( + "new {}({})", + csharp_type_name(&holder.model_name), + required.join(", ") + ); + if !optional.is_empty() { + expression.push_str(" { "); + expression.push_str(&optional.join(", ")); + expression.push_str(" }"); + } + expression +} + fn resource_return_binding_expr( resource: &crate::planning::PlannedOperationResourceReturn, binding: &PlannedOperationResourceFieldBinding, diff --git a/src/generator/go.rs b/src/generator/go.rs index cc61b1a5c..0e493f490 100644 --- a/src/generator/go.rs +++ b/src/generator/go.rs @@ -472,7 +472,13 @@ fn planned_value_type(value_type: &PlannedType, spec: &PlannedSpec) -> PlannedVa info: local_type_info(&variant_type.full_name), name: variant_type.name.clone(), }), - TypeSpec::Resource(_) => PlannedValueType::Unknown, + TypeSpec::Resource(resource) => PlannedValueType::External { + type_name: SelectedTextSpec { + value: Some(resource.type_name.clone()), + import: None, + }, + fallback: Box::new(PlannedValueType::Unknown), + }, TypeSpec::External(ExternalTypeSpec::Proto(PlannedProtoType::Message(message))) => { PlannedValueType::Message(PlannedMessageType { info: message.proto.clone(), @@ -2633,6 +2639,22 @@ impl GoVisibilityMarker<'_> { } PlannedOperationOutput::Resource { .. } | PlannedOperationOutput::None => {} } + // A result record that holds a resource is what the caller gets. + if let Some(holder) = operation + .data + .output_resource_return + .as_ref() + .and_then(|resource_return| resource_return.holder.as_ref()) + && self.public_keys.insert(holder.record_full_name.clone()) + && let Some(record) = self.api_plan.record(&holder.record_full_name) + { + for (_, field) in record.public_fields() { + self.mark_operation_field_kind( + &planned_field_kind(&field.field_type, self.api_plan), + field.name.as_str(), + ); + } + } } fn mark_resource_public_surface(&mut self, resource: &PlannedResource) { diff --git a/src/generator/proto/go.rs b/src/generator/proto/go.rs index 85898226f..2b7b8f3a5 100644 --- a/src/generator/proto/go.rs +++ b/src/generator/proto/go.rs @@ -814,6 +814,9 @@ struct RenderedResourceReturn { local_lines: Vec, field_initializers: Vec, requires_data_converter: bool, + /// The result record that holds the resource: its type, the resource field, and each + /// other field with its value. + holder: Option<(String, String, Vec<(String, String)>)>, } #[derive(Debug)] @@ -1285,20 +1288,38 @@ pub(in crate::generator) fn render_operation_function_proto( output.push_str("\t\t\treturn\n"); output.push_str("\t\t}\n"); for line in &resource_return.local_lines { + // The lines run in the future's goroutine, which sets the error. + if line.trim() == "return nil, err" { + let indent = &line[..line.len() - line.trim_start().len()]; + output.push_str(&format!( + "\t\t{indent}resultSettable.SetError(err)\n\t\t{indent}return\n" + )); + continue; + } output.push_str("\t\t"); output.push_str(line); output.push('\n'); } - output.push_str("\t\tvalue := New"); - output.push_str(&resource_return.resource_type_name); - output.push('('); + let mut resource = format!("New{}(", resource_return.resource_type_name); for (index, initializer) in resource_return.field_initializers.iter().enumerate() { if index > 0 { - output.push_str(", "); + resource.push_str(", "); + } + resource.push_str(&initializer.expr); + } + resource.push(')'); + output.push_str("\t\tvalue := "); + match &resource_return.holder { + Some((type_name, resource_field, fields)) => { + output.push_str(&format!("{type_name}{{{resource_field}: {resource}")); + for (name, expr) in fields { + output.push_str(&format!(", {name}: {expr}")); + } + output.push('}'); } - output.push_str(&initializer.expr); + None => output.push_str(&resource), } - output.push_str(")\n"); + output.push('\n'); }, ); } else { @@ -1435,11 +1456,67 @@ fn build_rendered_resource_return( field_initializers.push(RenderedResourceFieldInitializer { expr }); } + let holder = match &resource_return.holder { + Some(holder) => { + let record = api_plan + .record(&holder.record_full_name) + .expect("the plan has the holder record"); + let mut resource_field = String::new(); + let mut fields = Vec::new(); + for field in &holder.fields { + if field.resource { + resource_field = go_field_name(&field.name); + continue; + } + let planned = &record.fields[&field.proto_name]; + let resource_field = PlannedResourceField { + name: field.name.clone(), + optional: !field.required, + kind: planned.field_type.clone(), + function: None, + }; + let (lines, expr, uses_data_converter) = resource_return_proto_field_source( + &resource_field, + "result", + &field.proto_name, + api_plan, + backend, + package, + ) + .map_err(|reason| Error::UnsupportedGoProtoConversion { + context: format!("result record field `{}.{}`", holder.model_name, field.name), + reason, + })?; + requires_data_converter |= uses_data_converter; + local_lines.extend(lines); + // An optional scalar field of the record is a pointer, and the zero value + // means unset, as in the record's model. + let field_kind = planned_field_kind(&resource_field.kind, api_plan); + let field_type = resolve_resource_field_kind(&field_kind, true, package); + let expr = match public_default_punning_zero_for_field(&field_kind, &field_type) { + Some(zero) if !field.required => { + let local = format!("held{}", go_field_name(&field.name)); + local_lines.push(format!("var {local} {field_type}")); + local_lines.push(format!("if value := {expr}; value != {zero} {{")); + local_lines.push(format!("\t{local} = &value")); + local_lines.push("}".to_string()); + local + } + _ => expr, + }; + fields.push((go_field_name(&field.name), expr)); + } + Some((holder.model_name.clone(), resource_field, fields)) + } + None => None, + }; + Ok(RenderedResourceReturn { resource_type_name: resource_return.resource_type_name.clone(), local_lines, field_initializers, requires_data_converter, + holder, }) } diff --git a/src/generator/python.rs b/src/generator/python.rs index 613a2f8a2..f83bc0b85 100644 --- a/src/generator/python.rs +++ b/src/generator/python.rs @@ -15,9 +15,9 @@ use crate::language::Language; use crate::planning::{ PlannedFamily, PlannedOperationResourceFieldBinding, PlannedOperationResourceReturn, PlannedProtoType, PlannedProtoTypeInfo, PlannedRecordType, PlannedResource, - PlannedResourceField, PlannedResourceMethod, PlannedResourceMethodBindingSpec, - PlannedResourceMethodResultKind, PlannedSpec, PlannedType, message_model_name, - operation_input_model, operation_output_direct_result, + PlannedResourceField, PlannedResourceHolder, PlannedResourceMethod, + PlannedResourceMethodBindingSpec, PlannedResourceMethodResultKind, PlannedSpec, PlannedType, + message_model_name, operation_input_model, operation_output_direct_result, }; use crate::planning::{RequestPlan, ResolvedResourceBindingSource}; use crate::spec::{ @@ -524,7 +524,12 @@ impl<'a> ApiPlanner<'a> { }); self.resolve_message_value_conversion(&model_type)?; } - let model_refs = self.models.values().collect::>(); + let holders = resource_holders(api_plan); + let model_refs = self + .models + .values() + .filter(|model| !holders.contains_key(&model.full_name)) + .collect::>(); let variant_refs = self.variants.values().collect::>(); let model_fragments = self.render_model_fragments(model_refs.as_slice(), variant_refs.as_slice())?; @@ -602,6 +607,11 @@ impl<'a> ApiPlanner<'a> { .iter() .map(|resource| resource.type_name.clone()) }) + .chain( + resource_holders(self.api_plan) + .into_values() + .map(|(model_name, _)| model_name), + ) .collect::>() .into_iter() .collect::>(); @@ -684,7 +694,7 @@ impl<'a> ApiPlanner<'a> { if !resource_names.is_empty() { files.insert( "_resources/__init__.py", - render_resources_package_init(services), + render_resources_package_init(services, self.api_plan), GeneratedFileOrigin::fixed("generated Python resources package initializer"), )?; } @@ -814,6 +824,17 @@ impl<'a> ApiPlanner<'a> { body.push_str("\n\n"); } self.render_resource(&mut body, service, resource, bound_operations); + for (full_name, (_, resource_type)) in resource_holders(self.api_plan) { + if resource_type != resource.type_name { + continue; + } + let model = &self.models[&full_name]; + body.push_str("\n\n"); + render_record_model(&mut body, model, None); + for field in &model.fields { + module_imports.extend(field.imports.module_imports.iter().cloned()); + } + } if !bound_operations.is_empty() && service.endpoint.is_some() { body.push_str("\n\n"); for (index, bound_operation) in bound_operations.iter().enumerate() { @@ -872,9 +893,15 @@ impl<'a> ApiPlanner<'a> { self.model_hoists, ); } + let own_names = resource_holders(self.api_plan) + .into_values() + .filter(|(_, resource_type)| *resource_type == resource.type_name) + .map(|(model_name, _)| model_name) + .chain(std::iter::once(resource.type_name.clone())) + .collect::>(); let used_resource_names = used_python_symbol_imports(&body, resource_names) .into_iter() - .filter(|name| name != &resource.type_name) + .filter(|name| !own_names.contains(name)) .collect::>(); if !used_resource_names.is_empty() { render_named_python_import(&mut output, ".", &used_resource_names); @@ -1252,9 +1279,13 @@ impl<'a> ApiPlanner<'a> { let overload_output_annotation = output_transform .and_then(|transform| transform.type_name.value().map(str::to_string)) .or_else(|| { - output_resource_return - .as_ref() - .map(|resource| resource.resource_type_name.clone()) + output_resource_return.as_ref().map(|resource| { + resource + .holder + .as_ref() + .map(|holder| holder.model_name.clone()) + .unwrap_or_else(|| resource.resource_type_name.clone()) + }) }) .unwrap_or_else(|| output_annotation_default.clone()); let unpacked_input = if let (Some(input), Some(rendered_input)) = (input, &rendered_input) { @@ -3962,11 +3993,48 @@ fn render_models_module( Ok(Some(output)) } -fn render_resources_package_init(services: &[RenderedService<'_>]) -> String { +/// The result records that hold a resource, by full name, with the model name and the +/// resource type. Each one lives in its resource's module, since the resource module imports +/// the models and the record uses the resource class. +fn resource_holders(api_plan: &PlannedSpec) -> BTreeMap { + api_plan + .services + .iter() + .flat_map(|service| service.operations.iter()) + .filter_map(|operation| operation.data.output_resource_return.as_ref()) + .filter_map(|resource_return| { + resource_return.holder.as_ref().map(|holder| { + ( + holder.record_full_name.clone(), + ( + holder.model_name.clone(), + resource_return.resource_type_name.clone(), + ), + ) + }) + }) + .collect() +} + +fn render_resources_package_init( + services: &[RenderedService<'_>], + api_plan: &PlannedSpec, +) -> String { let resources = services .iter() .flat_map(|service| service.resources.iter()) .collect::>(); + let holders = resource_holders(api_plan); + let names_of = |resource: &PlannedResource| { + std::iter::once(resource.type_name.clone()) + .chain( + holders + .values() + .filter(|(_, resource_type)| *resource_type == resource.type_name) + .map(|(model_name, _)| model_name.clone()), + ) + .collect::>() + }; let mut output = String::new(); render_generated_file_header(&mut output); output.push('\n'); @@ -3974,14 +4042,16 @@ fn render_resources_package_init(services: &[RenderedService<'_>]) -> String { render_named_python_import( &mut output, &format!(".{}", resource_module_name(resource)), - std::slice::from_ref(&resource.type_name), + &names_of(resource), ); } output.push_str("\n__all__ = [\n"); for resource in &resources { - output.push_str(" "); - output.push_str(&python_string_literal(&resource.type_name)); - output.push_str(",\n"); + for name in names_of(resource) { + output.push_str(" "); + output.push_str(&python_string_literal(&name)); + output.push_str(",\n"); + } } output.push_str("]\n"); output @@ -6874,6 +6944,10 @@ fn render_request_only_operation_function( } else if let Some(resource_return) = &operation.output_resource_return { output.push_str(" result = await handle\n"); render_resource_return_guards(output, " ", operation, resource_return); + if let Some(holder) = &resource_return.holder { + render_resource_holder_return(output, resource_return, holder); + return; + } if service.endpoint.is_none() { output.push_str(" resource = "); } else { @@ -6896,6 +6970,41 @@ fn render_request_only_operation_function( } } +/// The holder record gets the resource in its resource field, and each other field from the +/// field of the same name in the wire record. +fn render_resource_holder_return( + output: &mut String, + resource_return: &PlannedOperationResourceReturn, + holder: &PlannedResourceHolder, +) { + output.push_str(" return "); + output.push_str(&holder.model_name); + output.push_str("(\n"); + for field in &holder.fields { + let name = python_field_name(&field.name); + output.push_str(" "); + output.push_str(&name); + if !field.resource { + output.push_str("=result."); + output.push_str(&name); + output.push_str(",\n"); + continue; + } + output.push('='); + output.push_str(&resource_return.resource_type_name); + output.push_str("(\n"); + for binding in &resource_return.bindings { + output.push_str(" "); + output.push_str(&python_field_name(&binding.field_name)); + output.push('='); + output.push_str(&resource_return_binding_expr_python(binding)); + output.push_str(",\n"); + } + output.push_str(" ),\n"); + } + output.push_str(" )\n"); +} + fn render_unpacked_operation_docstring( output: &mut String, operation: &RenderedOperation<'_>, diff --git a/src/generator/typescript.rs b/src/generator/typescript.rs index b64182311..254d22d99 100644 --- a/src/generator/typescript.rs +++ b/src/generator/typescript.rs @@ -517,9 +517,13 @@ impl<'a> ApiPlanner<'a> { output_annotation: output_transform .and_then(|transform| transform.type_name.value().map(str::to_string)) .or_else(|| { - output_resource_return - .as_ref() - .map(|resource| resource.resource_type_name.clone()) + output_resource_return.as_ref().map(|resource| { + resource + .holder + .as_ref() + .map_or(&resource.resource_type_name, |holder| &holder.model_name) + .clone() + }) }) .unwrap_or(output_annotation_default), output_transform_type_import: output_transform @@ -3891,6 +3895,16 @@ fn render_endpoint_service_operation_body( output.push_str(" return "); output.push_str(transform_expr); output.push_str(";\n"); + } else if let Some(resource_return) = &operation.output_resource_return + && resource_return.holder.is_some() + { + output.push_str(" const result = await handle.result();\n"); + output.push_str(" return "); + output.push_str(&typescript_resource_return_value( + resource_return, + Some("this.client"), + )); + output.push_str(";\n"); } else if let Some(resource_return) = &operation.output_resource_return { output.push_str(" const result = await handle.result();\n"); output.push_str(" return "); @@ -4270,6 +4284,20 @@ fn render_models_module( api_plan, &body, ); + // A result record that holds a resource uses its class. + let held_resources = api_plan + .services + .iter() + .flat_map(|service| service.operations.iter()) + .filter_map(|operation| operation.data.output_resource_return.as_ref()) + .filter(|resource_return| resource_return.holder.is_some()) + .map(|resource_return| resource_return.resource_type_name.clone()) + .collect::>() + .into_iter() + .collect::>(); + if !held_resources.is_empty() { + render_type_imports(&mut imports, "./resources", &held_resources); + } render_generated_module(imports, body) } @@ -6177,6 +6205,44 @@ fn render_request_plan_typescript(plan: &RequestPlan) -> String { ) } +/// The resource that an operation returns, bound to `client` when one is given, or the +/// result record that holds it. The record takes each other field from the field of the +/// same name in the wire record. +fn typescript_resource_return_value( + resource_return: &PlannedOperationResourceReturn, + client: Option<&str>, +) -> String { + let args = resource_return + .bindings + .iter() + .map(resource_return_binding_expr_typescript) + .collect::>() + .join(", "); + let mut value = format!("new {}({args})", resource_return.resource_type_name); + if let Some(client) = client { + value = format!( + "{}({value}, {client})", + resource_client_bind_function_name_for_type(&resource_return.resource_type_name) + ); + } + let Some(holder) = &resource_return.holder else { + return value; + }; + let fields = holder + .fields + .iter() + .map(|field| { + let name = typescript_generated_field_name(&field.name); + if field.resource { + format!("{name}: {value}") + } else { + format!("{name}: result.{name}") + } + }) + .collect::>(); + format!("{{ {} }}", fields.join(", ")) +} + fn resource_return_binding_expr_typescript( binding: &PlannedOperationResourceFieldBinding, ) -> String { @@ -6372,6 +6438,13 @@ fn render_operation_function( output.push_str(" return "); output.push_str(transform_expr); output.push_str(";\n"); + } else if let Some(resource_return) = &operation.output_resource_return + && resource_return.holder.is_some() + { + // The answer is the wire message, so the record and its resource are built from it. + output.push_str(" return "); + output.push_str(&typescript_resource_return_value(resource_return, None)); + output.push_str(";\n"); } else if let Some(resource_return) = &operation.output_resource_return { // The answer is the resource's wire message, so the resource is built from it. let args = resource_return @@ -6415,6 +6488,16 @@ fn render_operation_function( output.push_str(" return "); output.push_str(transform_expr); output.push_str(";\n"); + } else if let Some(resource_return) = &operation.output_resource_return + && resource_return.holder.is_some() + { + output.push_str(" const result = await handle.result();\n"); + output.push_str(" return "); + output.push_str(&typescript_resource_return_value( + resource_return, + service.endpoint.is_none().then_some("endpoint"), + )); + output.push_str(";\n"); } else if let Some(resource_return) = &operation.output_resource_return { output.push_str(" const result = await handle.result();\n"); if service.endpoint.is_none() { diff --git a/src/planning/authored_validation.rs b/src/planning/authored_validation.rs index fc9338b15..c38cbad8a 100644 --- a/src/planning/authored_validation.rs +++ b/src/planning/authored_validation.rs @@ -14,7 +14,7 @@ use crate::generator::python; use crate::language::Language; use crate::spec::{ ApiSpec, AuthoredFamily, ExternalTypeBindingSpec, ExternalTypeSourceSpec, ExternalTypeSpec, - RecordFieldVisibility, RecordSpec, ServiceSpec, TypeSpec, + RecordFieldSpec, RecordFieldVisibility, RecordSpec, ServiceSpec, TypeSpec, }; use crate::spec::{ApiSpecLeaf, CompilerPass}; @@ -492,7 +492,7 @@ fn validate_message_model_config( .record .fields .iter() - .filter(|(_, field)| field.required) + .filter(|(_, field)| field.required && !is_resource_field(field)) .map(|(field_name, _)| field_name) { if oneof_by_name(&oneofs, field_name).is_some() { @@ -512,8 +512,8 @@ fn validate_message_model_config( } validate_model_override_field(message_name, field_name, message)?; } - for field_name in model_config.record.fields.keys() { - if oneof_by_name(&oneofs, field_name).is_some() { + for (field_name, field) in &model_config.record.fields { + if oneof_by_name(&oneofs, field_name).is_some() || is_resource_field(field) { continue; } validate_model_override_field(message_name, field_name, message)?; @@ -551,7 +551,7 @@ fn validate_record_fields( language: Language, ) -> Result<()> { for (field_name, field) in &model_config.record.fields { - if oneof_by_name(oneofs, field_name).is_some() { + if oneof_by_name(oneofs, field_name).is_some() || is_resource_field(field) { continue; } validate_model_override_field(message_name, field_name, message)?; @@ -639,13 +639,17 @@ fn validate_record_fields( } } + // A record that holds a resource declares only what its caller sees. The resource binds + // the fields that it needs, and the other fields of the message are left out. + let holds_resource = model_config.record.fields.values().any(is_resource_field); let mut seen_generated_names: BTreeMap = BTreeMap::new(); for field in &message.descriptor.field { let proto_name = field .name .as_deref() .expect("descriptor fields should be named"); - if !model_config.is_field_omitted(proto_name) + if !holds_resource + && !model_config.is_field_omitted(proto_name) && !model_config.record.fields.contains_key(proto_name) && !oneofs.iter().any(|oneof| { model_config.record.fields.contains_key(oneof.name) @@ -701,6 +705,9 @@ fn validate_authored_field_types( continue; } + if is_resource_field(field_spec) { + continue; + } let field = validate_model_override_field(message_name, field_name, message)?; if model_config.is_field_omitted(field_name) { continue; @@ -1223,6 +1230,12 @@ fn validate_model_override_field<'a>( }) } +/// A resource field holds a resource that an operation result builds from the message, so +/// it has no proto field of its own. +fn is_resource_field(field: &RecordFieldSpec) -> bool { + matches!(field.field_type.without_option(), TypeSpec::Resource(_)) +} + fn validate_model_required_field( message_name: &str, field_name: &str, diff --git a/src/planning/mod.rs b/src/planning/mod.rs index 19e0dd256..918b0cee9 100644 --- a/src/planning/mod.rs +++ b/src/planning/mod.rs @@ -37,8 +37,8 @@ pub(crate) use proto::{message_model_name, relative_descriptor_name}; pub(crate) use reachability::ReachabilityPass; pub(crate) use resource_resolution::{ RequestPlan, RequestPlanField, RequestPlanSource, ResolvedResourceBindingSource, - ResolvedResourceMethodBinding, ResolvedResourceReturnSpec, ResolvedResourceSpec, - ResourceBoundData, ResourceBoundFamily, ResourceResolutionPass, + ResolvedResourceHolder, ResolvedResourceMethodBinding, ResolvedResourceReturnSpec, + ResolvedResourceSpec, ResourceBoundData, ResourceBoundFamily, ResourceResolutionPass, }; pub(crate) use selected_validation::SelectedValidationPass; pub(crate) use selection::LanguageSelectionPass; @@ -119,6 +119,25 @@ pub(crate) enum PlanningMode { pub(crate) struct PlannedOperationResourceReturn { pub(crate) resource_type_name: String, pub(crate) bindings: Vec, + pub(crate) holder: Option, +} + +/// The result record that holds the returned resource, by model and field names. Its other +/// fields have the same names in the wire record. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct PlannedResourceHolder { + pub(crate) record_full_name: String, + pub(crate) model_name: String, + /// The record's fields in order. The resource field holds the resource. + pub(crate) fields: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct PlannedResourceHolderField { + pub(crate) name: String, + pub(crate) proto_name: String, + pub(crate) required: bool, + pub(crate) resource: bool, } #[derive(Debug, Clone, PartialEq)] diff --git a/src/planning/operation_lowering.rs b/src/planning/operation_lowering.rs index b98daed73..bf95a23bf 100644 --- a/src/planning/operation_lowering.rs +++ b/src/planning/operation_lowering.rs @@ -59,12 +59,22 @@ impl CompilerPass for OperationLow ) -> Result> { let mut spec = leaf.spec.map_names(OperationLoweringMapper); let mut generated_records = Vec::new(); + let mut holders = Vec::new(); for service in &mut spec.services { for operation in &mut service.operations { let Some(resource_return) = operation.data.output_resource_return.as_ref() else { continue; }; + if let Some(holder) = &resource_return.holder { + holders.push(( + service.name.clone(), + operation.name.clone(), + holder.clone(), + resource_return.clone(), + )); + continue; + } let Some(TypeSpec::Resource(resource)) = operation.output_type() else { continue; }; @@ -104,6 +114,33 @@ impl CompilerPass for OperationLow } } + for (service_name, operation_name, holder, resource_return) in holders { + let (wire_full_name, wire_record) = lower_resource_holder( + &spec, + &service_name, + &operation_name, + &holder, + &resource_return, + )?; + if let Some(TypeDeclEntry { + declaration: TypeDeclSpec::Record(record), + .. + }) = spec.types.get_mut(&holder.record_name) + { + // The holder is the API model only. Its message travels as the wire record. + record.source = None; + } + let operation = spec + .services + .iter_mut() + .filter(|service| service.name == service_name) + .flat_map(|service| service.operations.iter_mut()) + .find(|operation| operation.name == operation_name) + .expect("the holder operation is in its service"); + operation.output = Some(TypeSpec::Record(Symbol::new(wire_full_name.clone()))); + generated_records.push((wire_full_name, wire_record)); + } + for (full_name, record) in generated_records { spec.types .entry(full_name) @@ -119,6 +156,69 @@ impl CompilerPass for OperationLow } } +/// The wire record of a result record that holds a resource. It takes the name of the proto +/// message, and has the holder's other fields plus the fields that the resource binds from. +fn lower_resource_holder( + spec: &crate::spec::ApiSpec, + service_name: &str, + operation_name: &str, + holder: &super::ResolvedResourceHolder, + resource_return: &super::ResolvedResourceReturnSpec, +) -> Result<(String, RecordSpec)> { + let record = spec + .record(&holder.record_name) + .expect("the holder record is declared"); + let wire_name = resource_return + .output_message_name + .rsplit('.') + .next() + .unwrap_or(&resource_return.output_message_name) + .to_string(); + if spec + .types + .values() + .any(|entry| { + matches!(&entry.declaration, TypeDeclSpec::Record(other) if other.name == wire_name) + }) + { + return Err(Error::InvalidResource { + service: service_name.to_string(), + resource: resource_return.resource_name.to_upper_camel_case(), + reason: format!( + "operation `{operation_name}` needs the name `{wire_name}` for the wire record \ + of `{}`, and a record already has it", + record.name + ), + }); + } + let mut fields = IndexMap::new(); + for (key, field) in &record.fields { + if key == &holder.field_key { + fields.extend(resource_result_fields(resource_return)); + } else { + fields.insert(key.clone(), field.clone()); + } + } + let full_name = format!("__generated.{service_name}.{wire_name}"); + Ok(( + full_name.clone(), + RecordSpec { + name: wire_name, + full_name, + doc: record.doc.clone(), + source: Some(ExternalTypeSourceSpec::Proto(ProtoTypeSpec { + proto: Symbol::new(resource_return.output_message_name.clone()), + reference: Default::default(), + type_name: Default::default(), + })), + experimental: record.experimental, + flatten_in_api: false, + fields, + data: (), + }, + )) +} + fn resource_result_fields( resource_return: &super::ResolvedResourceReturnSpec, ) -> IndexMap> { diff --git a/src/planning/reachability.rs b/src/planning/reachability.rs index d727c759a..073234e40 100644 --- a/src/planning/reachability.rs +++ b/src/planning/reachability.rs @@ -51,6 +51,14 @@ fn prune(spec: &mut PlannedSpec) { .flatten() .cloned(), ); + if let Some(holder) = operation + .data + .output_resource_return + .as_ref() + .and_then(|resource_return| resource_return.holder.as_ref()) + { + add_name(spec, &holder.record_full_name, &mut reachable); + } } for resource in &service.resources { enqueue_resource(&resource.data, &mut pending); diff --git a/src/planning/resource_resolution.rs b/src/planning/resource_resolution.rs index 72cdf9849..4cd468180 100644 --- a/src/planning/resource_resolution.rs +++ b/src/planning/resource_resolution.rs @@ -121,6 +121,16 @@ pub struct ResolvedResourceReturnSpec { pub resource_name: String, pub output_message_name: String, pub bindings: Vec, + /// Set when the operation returns a record that holds the resource in one field. + pub holder: Option, +} + +/// A result record with one resource field. The resource binds from the record's message, +/// and the other fields of the record come from the same message. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedResourceHolder { + pub record_name: String, + pub field_key: String, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -186,12 +196,19 @@ pub(crate) fn resolve_service_resources( let mut operation_returns = BTreeMap::new(); for operation in &service.operations { - let Some(resource_name) = operation_output_resource_name(service, operation) else { - continue; + let holder = operation_output_resource_holder(spec, service, operation)?; + let resource_name = match &holder { + Some((_, _, resource_name, _)) => *resource_name, + None => { + let Some(resource_name) = operation_output_resource_name(service, operation) else { + continue; + }; + if operation_output_resource_wire_type(operation).is_none() { + continue; + } + resource_name + } }; - if operation_output_resource_wire_type(operation).is_none() { - continue; - } let resource = service .resource(resource_name) .ok_or_else(|| Error::InvalidResource { @@ -212,7 +229,11 @@ pub(crate) fn resolve_service_resources( ), }); }; - let Some(output_message_name) = operation_output_resource_message_name(operation) else { + let Some(output_message_name) = holder + .as_ref() + .map(|(_, _, _, proto_name)| *proto_name) + .or_else(|| operation_output_resource_message_name(operation)) + else { return Err(Error::InvalidResource { service: service.name.clone(), resource: resource.name.to_upper_camel_case(), @@ -248,6 +269,10 @@ pub(crate) fn resolve_service_resources( resource_name: resource.name.clone(), output_message_name: output_message_name.to_string(), bindings, + holder: holder.map(|(record_name, field_key, _, _)| ResolvedResourceHolder { + record_name: record_name.to_string(), + field_key: field_key.to_string(), + }), }, ); } @@ -755,6 +780,69 @@ fn operation_output_resource_name<'a>( .map(|_| resource_name.as_str()) } +/// The proto-backed result record of an operation that holds a resource of the service, as +/// its record name, field key, resource name and proto message name. +fn operation_output_resource_holder<'a>( + spec: &'a ApiSpec, + service: &'a ServiceSpec, + operation: &'a OperationSpec, +) -> Result> { + let record = match operation.output_type() { + Some(TypeSpec::Record(record_name)) => spec.record(record_name.as_str()), + Some(TypeSpec::External(ExternalTypeSpec::Proto(proto_name))) => { + spec.record_for_proto(proto_name.as_str()) + } + _ => None, + }; + let Some(record) = record else { + return Ok(None); + }; + let resource_fields = record + .fields + .iter() + .filter_map(|(key, field)| match field.field_type.without_option() { + TypeSpec::Resource(resource) => Some((key.as_str(), resource.as_str())), + _ => None, + }) + .collect::>(); + let [(field_key, resource_name)] = resource_fields.as_slice() else { + if resource_fields.is_empty() { + return Ok(None); + } + return Err(Error::InvalidResource { + service: service.name.clone(), + resource: resource_fields[0].1.to_upper_camel_case(), + reason: format!( + "result record `{}` of operation `{}` holds more than one resource", + record.name, operation.name + ), + }); + }; + let Some(proto_name) = record + .source + .as_ref() + .and_then(|source| source.proto_type()) + else { + return Err(Error::InvalidResource { + service: service.name.clone(), + resource: resource_name.to_upper_camel_case(), + reason: format!( + "result record `{}` of operation `{}` holds a resource, so it needs `@nexus.proto`", + record.name, operation.name + ), + }); + }; + if service.resource(resource_name).is_none() { + return Ok(None); + } + Ok(Some(( + record.full_name.as_str(), + field_key, + resource_name, + proto_name.as_str(), + ))) +} + fn operation_output_ref(operation: &OperationSpec) -> Option<&str> { operation.output_type()?.reference() } diff --git a/src/planning/type_planning.rs b/src/planning/type_planning.rs index 67ac8a980..43a2593e4 100644 --- a/src/planning/type_planning.rs +++ b/src/planning/type_planning.rs @@ -665,6 +665,7 @@ impl<'a> TypePlanningContext<'a> { serialization_context: operation.serialization_context.clone(), data: PlannedOperationData { output_resource_return: plan_operation_resource_return( + &self.spec, operation.data.output_resource_return.as_ref(), ), }, @@ -1280,9 +1281,27 @@ impl<'a> TypePlanningContext<'a> { } fn plan_operation_resource_return( + spec: &ApiSpec, output_resource_return: Option<&ResolvedResourceReturnSpec>, ) -> Option { output_resource_return.map(|resource_return| PlannedOperationResourceReturn { + holder: resource_return.holder.as_ref().and_then(|holder| { + let record = spec.record(&holder.record_name)?; + Some(PlannedResourceHolder { + record_full_name: record.full_name.clone(), + model_name: record.name.clone(), + fields: record + .fields + .iter() + .map(|(key, field)| PlannedResourceHolderField { + name: field.name.clone(), + proto_name: key.clone(), + required: field.required, + resource: *key == holder.field_key, + }) + .collect(), + }) + }), resource_type_name: resource_return.resource_name.to_upper_camel_case(), bindings: resource_return .bindings diff --git a/xtask/src/validate.rs b/xtask/src/validate.rs index b21571bf2..ddaf7f8da 100644 --- a/xtask/src/validate.rs +++ b/xtask/src/validate.rs @@ -357,6 +357,12 @@ fn validate_dotnet(repo_root: &Path, log: &mut ValidationLog) -> Result<()> { "--nologo", ], )?; + run( + log, + &workflow_service_docs_root, + "dotnet", + &["build", "Nexgen.DotNetWorkflowRun.csproj", "--nologo"], + )?; Ok(()) } From 903eb5d80747b2ae243172f538958a0b6903065c Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Mon, 26 Oct 2026 12:52:21 -0700 Subject: [PATCH 3/6] Regenerated the workflow-handle .NET models, which main's validate flags. --- advanced/samples/dotnet/wit/workflow-handle/Models.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/advanced/samples/dotnet/wit/workflow-handle/Models.cs b/advanced/samples/dotnet/wit/workflow-handle/Models.cs index 60fc0dc8e..77b92b10a 100644 --- a/advanced/samples/dotnet/wit/workflow-handle/Models.cs +++ b/advanced/samples/dotnet/wit/workflow-handle/Models.cs @@ -5,7 +5,6 @@ using System; using System.CodeDom.Compiler; using System.Collections.Generic; -using Temporalio.Converters; using Nexgen.Support; namespace Temporalio.Workflows From ffe6ab610ac7497ca32655b4bbebb2cc171e8bc6 Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Mon, 26 Oct 2026 12:53:06 -0700 Subject: [PATCH 4/6] Renamed the workflow-run sample to proto-record-with-resource. The name now says what the sample checks. The WIT keeps its workflow run domain. --- advanced/samples/README.md | 16 ++++++++++------ .../samples/dotnet/Nexgen.DotNetExamples.csproj | 2 +- ... Nexgen.DotNetProtoRecordWithResource.csproj} | 6 +++--- .../Models.cs | 1 - .../Operations.cs | 0 .../Resources.cs | 0 .../Services.cs | 0 .../SystemNexusWorkflowOutboundInterceptor.cs | 0 .../workflowrunservice.go | 2 +- advanced/samples/inputs/examples.toml | 3 ++- ...ow-run.wit => proto-record-with-resource.wit} | 0 .../__init__.py | 0 .../_resources/__init__.py | 0 .../_resources/workflow_run.py | 0 .../_system_nexus_interceptor.py | 0 .../models.py | 0 .../operations/__init__.py | 0 .../operations/start_run.py | 0 .../services.py | 0 .../index.ts | 0 .../interceptors.ts | 0 .../models.ts | 0 .../operations/cancel-run.ts | 0 .../operations/start-run.ts | 0 .../registry.ts | 0 .../resources.ts | 0 .../services.ts | 0 xtask/src/validate.rs | 6 +++++- 28 files changed, 22 insertions(+), 14 deletions(-) rename advanced/samples/dotnet/{Nexgen.DotNetWorkflowRun.csproj => Nexgen.DotNetProtoRecordWithResource.csproj} (73%) rename advanced/samples/dotnet/wit/{workflow-run => proto-record-with-resource}/Models.cs (99%) rename advanced/samples/dotnet/wit/{workflow-run => proto-record-with-resource}/Operations.cs (100%) rename advanced/samples/dotnet/wit/{workflow-run => proto-record-with-resource}/Resources.cs (100%) rename advanced/samples/dotnet/wit/{workflow-run => proto-record-with-resource}/Services.cs (100%) rename advanced/samples/dotnet/wit/{workflow-run => proto-record-with-resource}/SystemNexusWorkflowOutboundInterceptor.cs (100%) rename advanced/samples/go/{workflowrun => protorecordwithresource}/workflowrunservice.go (99%) rename advanced/samples/inputs/{workflow-run.wit => proto-record-with-resource.wit} (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/__init__.py (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/_resources/__init__.py (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/_resources/workflow_run.py (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/_system_nexus_interceptor.py (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/models.py (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/operations/__init__.py (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/operations/start_run.py (100%) rename advanced/samples/python/wit/{workflow_run => proto_record_with_resource}/services.py (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/index.ts (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/interceptors.ts (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/models.ts (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/operations/cancel-run.ts (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/operations/start-run.ts (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/registry.ts (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/resources.ts (100%) rename advanced/samples/typescript/wit/{workflow-run => proto-record-with-resource}/services.ts (100%) diff --git a/advanced/samples/README.md b/advanced/samples/README.md index 9b66d9c0b..3eeefa495 100644 --- a/advanced/samples/README.md +++ b/advanced/samples/README.md @@ -148,16 +148,20 @@ records are proto-backed, as a stream service's are, on the sample proto in [`typescript/wit/stream-service/`](typescript/wit/stream-service/), [`typescript/tests/stream-service.test.ts`](typescript/tests/stream-service.test.ts) -### `workflow-run` +### `proto-record-with-resource` A System Nexus operation that returns a proto-backed record that holds a resource, next to the other fields of the same message. -- WIT: [`inputs/workflow-run.wit`](inputs/workflow-run.wit) -- .NET: [`dotnet/wit/workflow-run/`](dotnet/wit/workflow-run/) -- Go: [`go/workflowrun/`](go/workflowrun/) -- Python: [`python/wit/workflow_run/`](python/wit/workflow_run/) -- TypeScript: [`typescript/wit/workflow-run/`](typescript/wit/workflow-run/) +- WIT: + [`inputs/proto-record-with-resource.wit`](inputs/proto-record-with-resource.wit) +- .NET: + [`dotnet/wit/proto-record-with-resource/`](dotnet/wit/proto-record-with-resource/) +- Go: [`go/protorecordwithresource/`](go/protorecordwithresource/) +- Python: + [`python/wit/proto_record_with_resource/`](python/wit/proto_record_with_resource/) +- TypeScript: + [`typescript/wit/proto-record-with-resource/`](typescript/wit/proto-record-with-resource/) ## Supporting files diff --git a/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj b/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj index 726e62aa9..1f4812439 100644 --- a/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj +++ b/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj @@ -14,7 +14,7 @@ - + diff --git a/advanced/samples/dotnet/Nexgen.DotNetWorkflowRun.csproj b/advanced/samples/dotnet/Nexgen.DotNetProtoRecordWithResource.csproj similarity index 73% rename from advanced/samples/dotnet/Nexgen.DotNetWorkflowRun.csproj rename to advanced/samples/dotnet/Nexgen.DotNetProtoRecordWithResource.csproj index 575753194..e1a2242c7 100644 --- a/advanced/samples/dotnet/Nexgen.DotNetWorkflowRun.csproj +++ b/advanced/samples/dotnet/Nexgen.DotNetProtoRecordWithResource.csproj @@ -4,13 +4,13 @@ 9.0 enable false - Nexgen.DotNetWorkflowRun + Nexgen.DotNetProtoRecordWithResource - + diff --git a/advanced/samples/dotnet/wit/workflow-run/Models.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Models.cs similarity index 99% rename from advanced/samples/dotnet/wit/workflow-run/Models.cs rename to advanced/samples/dotnet/wit/proto-record-with-resource/Models.cs index 60a65b703..8f31f54cf 100644 --- a/advanced/samples/dotnet/wit/workflow-run/Models.cs +++ b/advanced/samples/dotnet/wit/proto-record-with-resource/Models.cs @@ -5,7 +5,6 @@ using System; using System.CodeDom.Compiler; using System.Collections.Generic; -using Temporalio.Converters; using Nexgen.Support; namespace Temporalio.Workflows diff --git a/advanced/samples/dotnet/wit/workflow-run/Operations.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Operations.cs similarity index 100% rename from advanced/samples/dotnet/wit/workflow-run/Operations.cs rename to advanced/samples/dotnet/wit/proto-record-with-resource/Operations.cs diff --git a/advanced/samples/dotnet/wit/workflow-run/Resources.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Resources.cs similarity index 100% rename from advanced/samples/dotnet/wit/workflow-run/Resources.cs rename to advanced/samples/dotnet/wit/proto-record-with-resource/Resources.cs diff --git a/advanced/samples/dotnet/wit/workflow-run/Services.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Services.cs similarity index 100% rename from advanced/samples/dotnet/wit/workflow-run/Services.cs rename to advanced/samples/dotnet/wit/proto-record-with-resource/Services.cs diff --git a/advanced/samples/dotnet/wit/workflow-run/SystemNexusWorkflowOutboundInterceptor.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/SystemNexusWorkflowOutboundInterceptor.cs similarity index 100% rename from advanced/samples/dotnet/wit/workflow-run/SystemNexusWorkflowOutboundInterceptor.cs rename to advanced/samples/dotnet/wit/proto-record-with-resource/SystemNexusWorkflowOutboundInterceptor.cs diff --git a/advanced/samples/go/workflowrun/workflowrunservice.go b/advanced/samples/go/protorecordwithresource/workflowrunservice.go similarity index 99% rename from advanced/samples/go/workflowrun/workflowrunservice.go rename to advanced/samples/go/protorecordwithresource/workflowrunservice.go index 10cad9dd8..a1fe80417 100644 --- a/advanced/samples/go/workflowrun/workflowrunservice.go +++ b/advanced/samples/go/protorecordwithresource/workflowrunservice.go @@ -1,6 +1,6 @@ // Code generated by nexgen latest. DO NOT EDIT. -package workflowrun +package protorecordwithresource import ( "time" diff --git a/advanced/samples/inputs/examples.toml b/advanced/samples/inputs/examples.toml index f4f999946..6630ad9cf 100644 --- a/advanced/samples/inputs/examples.toml +++ b/advanced/samples/inputs/examples.toml @@ -26,7 +26,8 @@ descriptors = "temporal" languages = ["python", "dotnet", "typescript"] descriptors = "temporal" -[workflow-run] +# A proto-backed result record that holds a resource next to its other fields. +[proto-record-with-resource] system_nexus = true languages = ["python", "typescript", "dotnet", "go"] descriptors = "temporal" diff --git a/advanced/samples/inputs/workflow-run.wit b/advanced/samples/inputs/proto-record-with-resource.wit similarity index 100% rename from advanced/samples/inputs/workflow-run.wit rename to advanced/samples/inputs/proto-record-with-resource.wit diff --git a/advanced/samples/python/wit/workflow_run/__init__.py b/advanced/samples/python/wit/proto_record_with_resource/__init__.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/__init__.py rename to advanced/samples/python/wit/proto_record_with_resource/__init__.py diff --git a/advanced/samples/python/wit/workflow_run/_resources/__init__.py b/advanced/samples/python/wit/proto_record_with_resource/_resources/__init__.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/_resources/__init__.py rename to advanced/samples/python/wit/proto_record_with_resource/_resources/__init__.py diff --git a/advanced/samples/python/wit/workflow_run/_resources/workflow_run.py b/advanced/samples/python/wit/proto_record_with_resource/_resources/workflow_run.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/_resources/workflow_run.py rename to advanced/samples/python/wit/proto_record_with_resource/_resources/workflow_run.py diff --git a/advanced/samples/python/wit/workflow_run/_system_nexus_interceptor.py b/advanced/samples/python/wit/proto_record_with_resource/_system_nexus_interceptor.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/_system_nexus_interceptor.py rename to advanced/samples/python/wit/proto_record_with_resource/_system_nexus_interceptor.py diff --git a/advanced/samples/python/wit/workflow_run/models.py b/advanced/samples/python/wit/proto_record_with_resource/models.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/models.py rename to advanced/samples/python/wit/proto_record_with_resource/models.py diff --git a/advanced/samples/python/wit/workflow_run/operations/__init__.py b/advanced/samples/python/wit/proto_record_with_resource/operations/__init__.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/operations/__init__.py rename to advanced/samples/python/wit/proto_record_with_resource/operations/__init__.py diff --git a/advanced/samples/python/wit/workflow_run/operations/start_run.py b/advanced/samples/python/wit/proto_record_with_resource/operations/start_run.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/operations/start_run.py rename to advanced/samples/python/wit/proto_record_with_resource/operations/start_run.py diff --git a/advanced/samples/python/wit/workflow_run/services.py b/advanced/samples/python/wit/proto_record_with_resource/services.py similarity index 100% rename from advanced/samples/python/wit/workflow_run/services.py rename to advanced/samples/python/wit/proto_record_with_resource/services.py diff --git a/advanced/samples/typescript/wit/workflow-run/index.ts b/advanced/samples/typescript/wit/proto-record-with-resource/index.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/index.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/index.ts diff --git a/advanced/samples/typescript/wit/workflow-run/interceptors.ts b/advanced/samples/typescript/wit/proto-record-with-resource/interceptors.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/interceptors.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/interceptors.ts diff --git a/advanced/samples/typescript/wit/workflow-run/models.ts b/advanced/samples/typescript/wit/proto-record-with-resource/models.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/models.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/models.ts diff --git a/advanced/samples/typescript/wit/workflow-run/operations/cancel-run.ts b/advanced/samples/typescript/wit/proto-record-with-resource/operations/cancel-run.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/operations/cancel-run.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/operations/cancel-run.ts diff --git a/advanced/samples/typescript/wit/workflow-run/operations/start-run.ts b/advanced/samples/typescript/wit/proto-record-with-resource/operations/start-run.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/operations/start-run.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/operations/start-run.ts diff --git a/advanced/samples/typescript/wit/workflow-run/registry.ts b/advanced/samples/typescript/wit/proto-record-with-resource/registry.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/registry.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/registry.ts diff --git a/advanced/samples/typescript/wit/workflow-run/resources.ts b/advanced/samples/typescript/wit/proto-record-with-resource/resources.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/resources.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/resources.ts diff --git a/advanced/samples/typescript/wit/workflow-run/services.ts b/advanced/samples/typescript/wit/proto-record-with-resource/services.ts similarity index 100% rename from advanced/samples/typescript/wit/workflow-run/services.ts rename to advanced/samples/typescript/wit/proto-record-with-resource/services.ts diff --git a/xtask/src/validate.rs b/xtask/src/validate.rs index ddaf7f8da..cbb36f432 100644 --- a/xtask/src/validate.rs +++ b/xtask/src/validate.rs @@ -361,7 +361,11 @@ fn validate_dotnet(repo_root: &Path, log: &mut ValidationLog) -> Result<()> { log, &workflow_service_docs_root, "dotnet", - &["build", "Nexgen.DotNetWorkflowRun.csproj", "--nologo"], + &[ + "build", + "Nexgen.DotNetProtoRecordWithResource.csproj", + "--nologo", + ], )?; Ok(()) } From 9a292c5c03ecfb3e4876f806de6be406bfb79779 Mon Sep 17 00:00:00 2001 From: Moe Abadi Date: Mon, 26 Oct 2026 13:22:13 -0700 Subject: [PATCH 5/6] Added a proto-backed status enum to the stream-service create result. (#282) The stream service answers a create with the stream's status. Today's compiler plans the proto-backed enum, so the sample needs no compiler change. --- advanced/samples/README.md | 3 +- .../samples/descriptors/stream_service.bin | Bin 1752 -> 1932 bytes .../streamsample/v1/stream_service.proto | 8 ++ .../samples/dotnet/proto/StreamService.cs | 99 +++++++++++++----- .../dotnet/wit/stream-service/Models.cs | 16 +++ .../dotnet/wit/stream-service/Operations.cs | 2 +- advanced/samples/inputs/stream-service.wit | 9 ++ .../streamsample/v1/stream_service_pb2.py | 40 +++---- .../streamsample/v1/stream_service_pb2.pyi | 25 ++++- .../wit/stream_service/_resources/stream.py | 2 + .../python/wit/stream_service/models.py | 14 +++ .../operations/create_stream.py | 1 + .../typescript/proto/streamsample.d.ts | 8 ++ .../typescript/wit/stream-service/models.ts | 14 +++ .../operations/create-stream.ts | 1 + 15 files changed, 196 insertions(+), 46 deletions(-) diff --git a/advanced/samples/README.md b/advanced/samples/README.md index 3eeefa495..be891500c 100644 --- a/advanced/samples/README.md +++ b/advanced/samples/README.md @@ -221,4 +221,5 @@ protoc -I descriptors -I "$API" --csharp_out=dotnet/proto streamsample/v1/stream ``` The TypeScript `typescript/proto/streamsample.d.ts` mirrors the proto by hand, -with only the `I` interfaces that the generated code imports. +with only the `I` interfaces and the enums that the generated code +imports. diff --git a/advanced/samples/descriptors/stream_service.bin b/advanced/samples/descriptors/stream_service.bin index 5a375ff375af0b8d27ff3da31e17f902b35207aa..8dd29f09ace0cbcbe3ef97a4ad68bc52094f75fc 100644 GIT binary patch delta 192 zcmcb?+r!Vy)ycn+S)GY#HsfS%rW64oQ!cjRlEjkIVhLsiMh(8n4Xl!U!C-+PsKn-e zCS8_#ttu{_;F6-$#9WB1kSv#Ea7d7=qi=k0h+{}-aD1p=aDc0`r< @@ -1165,6 +1179,7 @@ public CreateStreamResponse() { public CreateStreamResponse(CreateStreamResponse other) : this() { stream_ = other.stream_ != null ? other.stream_.Clone() : null; created_ = other.created_; + status_ = other.status_; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } @@ -1198,6 +1213,18 @@ public bool Created { } } + /// Field number for the "status" field. + public const int StatusFieldNumber = 3; + private global::StreamSample.V1.StreamStatus status_ = global::StreamSample.V1.StreamStatus.Unspecified; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::StreamSample.V1.StreamStatus Status { + get { return status_; } + set { + status_ = value; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public override bool Equals(object other) { @@ -1215,6 +1242,7 @@ public bool Equals(CreateStreamResponse other) { } if (!object.Equals(Stream, other.Stream)) return false; if (Created != other.Created) return false; + if (Status != other.Status) return false; return Equals(_unknownFields, other._unknownFields); } @@ -1224,6 +1252,7 @@ public override int GetHashCode() { int hash = 1; if (stream_ != null) hash ^= Stream.GetHashCode(); if (Created != false) hash ^= Created.GetHashCode(); + if (Status != global::StreamSample.V1.StreamStatus.Unspecified) hash ^= Status.GetHashCode(); if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); } @@ -1250,6 +1279,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(16); output.WriteBool(Created); } + if (Status != global::StreamSample.V1.StreamStatus.Unspecified) { + output.WriteRawTag(24); + output.WriteEnum((int) Status); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -1268,6 +1301,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(16); output.WriteBool(Created); } + if (Status != global::StreamSample.V1.StreamStatus.Unspecified) { + output.WriteRawTag(24); + output.WriteEnum((int) Status); + } if (_unknownFields != null) { _unknownFields.WriteTo(ref output); } @@ -1284,6 +1321,9 @@ public int CalculateSize() { if (Created != false) { size += 1 + 1; } + if (Status != global::StreamSample.V1.StreamStatus.Unspecified) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status); + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -1305,6 +1345,9 @@ public void MergeFrom(CreateStreamResponse other) { if (other.Created != false) { Created = other.Created; } + if (other.Status != global::StreamSample.V1.StreamStatus.Unspecified) { + Status = other.Status; + } _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } @@ -1335,6 +1378,10 @@ public void MergeFrom(pb::CodedInputStream input) { Created = input.ReadBool(); break; } + case 24: { + Status = (global::StreamSample.V1.StreamStatus) input.ReadEnum(); + break; + } } } #endif @@ -1365,6 +1412,10 @@ public void MergeFrom(pb::CodedInputStream input) { Created = input.ReadBool(); break; } + case 24: { + Status = (global::StreamSample.V1.StreamStatus) input.ReadEnum(); + break; + } } } } diff --git a/advanced/samples/dotnet/wit/stream-service/Models.cs b/advanced/samples/dotnet/wit/stream-service/Models.cs index 1bc6175a3..0e8502d4b 100644 --- a/advanced/samples/dotnet/wit/stream-service/Models.cs +++ b/advanced/samples/dotnet/wit/stream-service/Models.cs @@ -10,12 +10,22 @@ namespace Nexgen.StreamService { + [GeneratedCode("nexgen", null)] + public enum StreamStatus + { + StreamStatusUnspecified = 0, + StreamStatusOpen = 1, + StreamStatusClosed = 2, + StreamStatusExpired = 3, + } + [Temporalio.Converters.TemporalTransferTypeConverter(typeof(CreateStreamResponse.TransferTypeConverter))] [GeneratedCode("nexgen", null)] public record CreateStreamResponse { public StreamRef? Stream { get; init; } public bool? Created { get; init; } + public StreamStatus? Status { get; init; } internal static CreateStreamResponse FromTransferType(StreamSample.V1.CreateStreamResponse wire) { @@ -23,6 +33,7 @@ internal static CreateStreamResponse FromTransferType(StreamSample.V1.CreateStre { Stream = wire.Stream == null ? null : StreamRef.FromTransferType(wire.Stream), Created = wire.Created == false ? null : wire.Created, + Status = (int)wire.Status == 0 ? null : (StreamStatus)wire.Status, }; } @@ -37,6 +48,10 @@ internal StreamSample.V1.CreateStreamResponse ToTransferType() { proto.Created = created; } + if (Status is { } status) + { + proto.Status = (StreamSample.V1.StreamStatus)status; + } return proto; } @@ -346,6 +361,7 @@ public CreateStreamResult(Stream stream) public Stream Stream { get; init; } public bool? Created { get; init; } + public StreamStatus? Status { get; init; } } [Temporalio.Converters.TemporalTransferTypeConverter(typeof(DescribeStreamRequest.TransferTypeConverter))] diff --git a/advanced/samples/dotnet/wit/stream-service/Operations.cs b/advanced/samples/dotnet/wit/stream-service/Operations.cs index 7c137b3fa..262510dbf 100644 --- a/advanced/samples/dotnet/wit/stream-service/Operations.cs +++ b/advanced/samples/dotnet/wit/stream-service/Operations.cs @@ -83,7 +83,7 @@ private static async Task CreateStreamAsync(CreateStreamRequ { var client = Workflow.CreateNexusWorkflowClient(StreamServiceEndpoint); var result = await client.ExecuteNexusOperationAsync(svc => svc.CreateStream(request)).ConfigureAwait(true); - return new CreateStreamResult(new Stream((result.Stream ?? throw new System.InvalidOperationException("CreateStream answered without `stream`")))) { Created = result.Created }; + return new CreateStreamResult(new Stream((result.Stream ?? throw new System.InvalidOperationException("CreateStream answered without `stream`")))) { Created = result.Created, Status = result.Status }; } /// Options for the operation. diff --git a/advanced/samples/inputs/stream-service.wit b/advanced/samples/inputs/stream-service.wit index 8fa7d8dc1..a1d7bb56b 100644 --- a/advanced/samples/inputs/stream-service.wit +++ b/advanced/samples/inputs/stream-service.wit @@ -31,6 +31,14 @@ interface stream-service { body: option, } + /// @nexus.proto "streamsample.v1.StreamStatus" typescript-import="#streamsample" + enum stream-status { + stream-status-unspecified, + stream-status-open, + stream-status-closed, + stream-status-expired, + } + /// @nexus.proto "streamsample.v1.CreateStreamRequest" typescript-import="#streamsample" record create-stream-request { %ref: option, @@ -123,6 +131,7 @@ interface stream-service { record create-stream-result { %stream: own<%stream>, created: option, + status: option, } /// @nexus.operation name="CreateStream" diff --git a/advanced/samples/python/streamsample/v1/stream_service_pb2.py b/advanced/samples/python/streamsample/v1/stream_service_pb2.py index 0e5956a09..30bb8138a 100644 --- a/advanced/samples/python/streamsample/v1/stream_service_pb2.py +++ b/advanced/samples/python/streamsample/v1/stream_service_pb2.py @@ -26,7 +26,7 @@ from temporalio.api.common.v1 import message_pb2 as temporal_dot_api_dot_common_dot_v1_dot_message__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$streamsample/v1/stream_service.proto\x12\x0fstreamsample.v1\x1a\x1egoogle/protobuf/duration.proto\x1a$temporal/api/common/v1/message.proto\",\n\tStreamRef\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"L\n\x0c\x41ppendRecord\x12\r\n\x05topic\x18\x01 \x01(\t\x12-\n\x04\x62ody\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"\\\n\x0cStreamRecord\x12\x0e\n\x06offset\x18\x01 \x01(\x03\x12\r\n\x05topic\x18\x02 \x01(\t\x12-\n\x04\x62ody\x18\x03 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"\x7f\n\x13\x43reateStreamRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12\'\n\x03ref\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12,\n\tretention\x18\x03 \x01(\x0b\x32\x19.google.protobuf.Duration\"S\n\x14\x43reateStreamResponse\x12*\n\x06stream\x18\x01 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12\x0f\n\x07\x63reated\x18\x02 \x01(\x08\"\x97\x01\n\x14\x41ppendRecordsRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12\x10\n\x08sequence\x18\x03 \x01(\x03\x12.\n\x07records\x18\x04 \x03(\x0b\x32\x1d.streamsample.v1.AppendRecord\"B\n\x15\x41ppendRecordsResponse\x12\x14\n\x0c\x66irst_offset\x18\x01 \x01(\x03\x12\x13\n\x0blast_offset\x18\x02 \x01(\x03\"\x84\x01\n\x12\x43loseStreamRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12/\n\x06result\x18\x03 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"\x15\n\x13\x43loseStreamResponse\"\xb6\x01\n\x12ReadRecordsRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12\x13\n\x0b\x66rom_offset\x18\x03 \x01(\x03\x12\x0e\n\x06topics\x18\x04 \x03(\t\x12\x13\n\x0bmax_records\x18\x05 \x01(\x05\x12\'\n\x04wait\x18\x06 \x01(\x0b\x32\x19.google.protobuf.Duration\"h\n\x13ReadRecordsResponse\x12.\n\x07records\x18\x01 \x03(\x0b\x32\x1d.streamsample.v1.StreamRecord\x12\x13\n\x0bnext_offset\x18\x02 \x01(\x03\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\"V\n\x15\x44\x65scribeStreamRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\"[\n\x16\x44\x65scribeStreamResponse\x12\x13\n\x0blast_offset\x18\x01 \x01(\x03\x12,\n\tretention\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x12\xaa\x02\x0fStreamSample.V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$streamsample/v1/stream_service.proto\x12\x0fstreamsample.v1\x1a\x1egoogle/protobuf/duration.proto\x1a$temporal/api/common/v1/message.proto\",\n\tStreamRef\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"L\n\x0c\x41ppendRecord\x12\r\n\x05topic\x18\x01 \x01(\t\x12-\n\x04\x62ody\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"\\\n\x0cStreamRecord\x12\x0e\n\x06offset\x18\x01 \x01(\x03\x12\r\n\x05topic\x18\x02 \x01(\t\x12-\n\x04\x62ody\x18\x03 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"\x7f\n\x13\x43reateStreamRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12\'\n\x03ref\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12,\n\tretention\x18\x03 \x01(\x0b\x32\x19.google.protobuf.Duration\"\x82\x01\n\x14\x43reateStreamResponse\x12*\n\x06stream\x18\x01 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12\x0f\n\x07\x63reated\x18\x02 \x01(\x08\x12-\n\x06status\x18\x03 \x01(\x0e\x32\x1d.streamsample.v1.StreamStatus\"\x97\x01\n\x14\x41ppendRecordsRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12\x10\n\x08sequence\x18\x03 \x01(\x03\x12.\n\x07records\x18\x04 \x03(\x0b\x32\x1d.streamsample.v1.AppendRecord\"B\n\x15\x41ppendRecordsResponse\x12\x14\n\x0c\x66irst_offset\x18\x01 \x01(\x03\x12\x13\n\x0blast_offset\x18\x02 \x01(\x03\"\x84\x01\n\x12\x43loseStreamRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12/\n\x06result\x18\x03 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"\x15\n\x13\x43loseStreamResponse\"\xb6\x01\n\x12ReadRecordsRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\x12\x13\n\x0b\x66rom_offset\x18\x03 \x01(\x03\x12\x0e\n\x06topics\x18\x04 \x03(\t\x12\x13\n\x0bmax_records\x18\x05 \x01(\x05\x12\'\n\x04wait\x18\x06 \x01(\x0b\x32\x19.google.protobuf.Duration\"h\n\x13ReadRecordsResponse\x12.\n\x07records\x18\x01 \x03(\x0b\x32\x1d.streamsample.v1.StreamRecord\x12\x13\n\x0bnext_offset\x18\x02 \x01(\x03\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\"V\n\x15\x44\x65scribeStreamRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12*\n\x06stream\x18\x02 \x01(\x0b\x32\x1a.streamsample.v1.StreamRef\"[\n\x16\x44\x65scribeStreamResponse\x12\x13\n\x0blast_offset\x18\x01 \x01(\x03\x12,\n\tretention\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration*z\n\x0cStreamStatus\x12\x1d\n\x19STREAM_STATUS_UNSPECIFIED\x10\x00\x12\x16\n\x12STREAM_STATUS_OPEN\x10\x01\x12\x18\n\x14STREAM_STATUS_CLOSED\x10\x02\x12\x19\n\x15STREAM_STATUS_EXPIRED\x10\x03\x42\x12\xaa\x02\x0fStreamSample.V1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -34,6 +34,8 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'\252\002\017StreamSample.V1' + _globals['_STREAMSTATUS']._serialized_start=1459 + _globals['_STREAMSTATUS']._serialized_end=1581 _globals['_STREAMREF']._serialized_start=127 _globals['_STREAMREF']._serialized_end=171 _globals['_APPENDRECORD']._serialized_start=173 @@ -42,22 +44,22 @@ _globals['_STREAMRECORD']._serialized_end=343 _globals['_CREATESTREAMREQUEST']._serialized_start=345 _globals['_CREATESTREAMREQUEST']._serialized_end=472 - _globals['_CREATESTREAMRESPONSE']._serialized_start=474 - _globals['_CREATESTREAMRESPONSE']._serialized_end=557 - _globals['_APPENDRECORDSREQUEST']._serialized_start=560 - _globals['_APPENDRECORDSREQUEST']._serialized_end=711 - _globals['_APPENDRECORDSRESPONSE']._serialized_start=713 - _globals['_APPENDRECORDSRESPONSE']._serialized_end=779 - _globals['_CLOSESTREAMREQUEST']._serialized_start=782 - _globals['_CLOSESTREAMREQUEST']._serialized_end=914 - _globals['_CLOSESTREAMRESPONSE']._serialized_start=916 - _globals['_CLOSESTREAMRESPONSE']._serialized_end=937 - _globals['_READRECORDSREQUEST']._serialized_start=940 - _globals['_READRECORDSREQUEST']._serialized_end=1122 - _globals['_READRECORDSRESPONSE']._serialized_start=1124 - _globals['_READRECORDSRESPONSE']._serialized_end=1228 - _globals['_DESCRIBESTREAMREQUEST']._serialized_start=1230 - _globals['_DESCRIBESTREAMREQUEST']._serialized_end=1316 - _globals['_DESCRIBESTREAMRESPONSE']._serialized_start=1318 - _globals['_DESCRIBESTREAMRESPONSE']._serialized_end=1409 + _globals['_CREATESTREAMRESPONSE']._serialized_start=475 + _globals['_CREATESTREAMRESPONSE']._serialized_end=605 + _globals['_APPENDRECORDSREQUEST']._serialized_start=608 + _globals['_APPENDRECORDSREQUEST']._serialized_end=759 + _globals['_APPENDRECORDSRESPONSE']._serialized_start=761 + _globals['_APPENDRECORDSRESPONSE']._serialized_end=827 + _globals['_CLOSESTREAMREQUEST']._serialized_start=830 + _globals['_CLOSESTREAMREQUEST']._serialized_end=962 + _globals['_CLOSESTREAMRESPONSE']._serialized_start=964 + _globals['_CLOSESTREAMRESPONSE']._serialized_end=985 + _globals['_READRECORDSREQUEST']._serialized_start=988 + _globals['_READRECORDSREQUEST']._serialized_end=1170 + _globals['_READRECORDSRESPONSE']._serialized_start=1172 + _globals['_READRECORDSRESPONSE']._serialized_end=1276 + _globals['_DESCRIBESTREAMREQUEST']._serialized_start=1278 + _globals['_DESCRIBESTREAMREQUEST']._serialized_end=1364 + _globals['_DESCRIBESTREAMRESPONSE']._serialized_start=1366 + _globals['_DESCRIBESTREAMRESPONSE']._serialized_end=1457 # @@protoc_insertion_point(module_scope) diff --git a/advanced/samples/python/streamsample/v1/stream_service_pb2.pyi b/advanced/samples/python/streamsample/v1/stream_service_pb2.pyi index f55bb08c8..07b7eba77 100644 --- a/advanced/samples/python/streamsample/v1/stream_service_pb2.pyi +++ b/advanced/samples/python/streamsample/v1/stream_service_pb2.pyi @@ -8,6 +8,7 @@ from google.protobuf import descriptor as _descriptor from google.protobuf import duration_pb2 as _duration_pb2 from google.protobuf import message as _message from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from temporalio.api.common.v1 import message_pb2 as _message_pb2 import builtins as _builtins import sys @@ -20,6 +21,25 @@ else: DESCRIPTOR: _descriptor.FileDescriptor +class _StreamStatus: + ValueType = _typing.NewType("ValueType", _builtins.int) + V: _TypeAlias = ValueType # noqa: Y015 + +class _StreamStatusEnumTypeWrapper(_enum_type_wrapper._EnumTypeWrapper[_StreamStatus.ValueType], _builtins.type): + DESCRIPTOR: _descriptor.EnumDescriptor + STREAM_STATUS_UNSPECIFIED: _StreamStatus.ValueType # 0 + STREAM_STATUS_OPEN: _StreamStatus.ValueType # 1 + STREAM_STATUS_CLOSED: _StreamStatus.ValueType # 2 + STREAM_STATUS_EXPIRED: _StreamStatus.ValueType # 3 + +class StreamStatus(_StreamStatus, metaclass=_StreamStatusEnumTypeWrapper): ... + +STREAM_STATUS_UNSPECIFIED: StreamStatus.ValueType # 0 +STREAM_STATUS_OPEN: StreamStatus.ValueType # 1 +STREAM_STATUS_CLOSED: StreamStatus.ValueType # 2 +STREAM_STATUS_EXPIRED: StreamStatus.ValueType # 3 +Global___StreamStatus: _TypeAlias = StreamStatus # noqa: Y015 + @_typing.final class StreamRef(_message.Message): DESCRIPTOR: _descriptor.Descriptor @@ -124,7 +144,9 @@ class CreateStreamResponse(_message.Message): STREAM_FIELD_NUMBER: _builtins.int CREATED_FIELD_NUMBER: _builtins.int + STATUS_FIELD_NUMBER: _builtins.int created: _builtins.bool + status: Global___StreamStatus.ValueType @_builtins.property def stream(self) -> Global___StreamRef: ... def __init__( @@ -132,10 +154,11 @@ class CreateStreamResponse(_message.Message): *, stream: Global___StreamRef | None = ..., created: _builtins.bool = ..., + status: Global___StreamStatus.ValueType = ..., ) -> None: ... _HasFieldArgType: _TypeAlias = _typing.Literal["stream", b"stream"] # noqa: Y015 def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... - _ClearFieldArgType: _TypeAlias = _typing.Literal["created", b"created", "stream", b"stream"] # noqa: Y015 + _ClearFieldArgType: _TypeAlias = _typing.Literal["created", b"created", "status", b"status", "stream", b"stream"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... def WhichOneof(self, oneof_group: _Never) -> None: ... diff --git a/advanced/samples/python/wit/stream_service/_resources/stream.py b/advanced/samples/python/wit/stream_service/_resources/stream.py index a045fed29..280d01a42 100644 --- a/advanced/samples/python/wit/stream_service/_resources/stream.py +++ b/advanced/samples/python/wit/stream_service/_resources/stream.py @@ -18,6 +18,7 @@ ReadRecordsRequest, ReadRecordsResponse, StreamRef, + StreamStatus, ) @@ -80,6 +81,7 @@ async def describe( class CreateStreamResult: stream: Stream created: bool | None = None + status: StreamStatus | None = None async def _append_records( diff --git a/advanced/samples/python/wit/stream_service/models.py b/advanced/samples/python/wit/stream_service/models.py index 8d0a1b48d..40a75f82c 100644 --- a/advanced/samples/python/wit/stream_service/models.py +++ b/advanced/samples/python/wit/stream_service/models.py @@ -3,6 +3,7 @@ from __future__ import annotations import dataclasses +import enum import typing import typing_extensions import datetime @@ -12,6 +13,13 @@ import temporal_support as _support +class StreamStatus(enum.IntEnum): + StreamStatusUnspecified = 0 + StreamStatusOpen = 1 + StreamStatusClosed = 2 + StreamStatusExpired = 3 + + @dataclasses.dataclass(slots=True) class CreateStreamRequest: ref: StreamRef | None = None @@ -116,6 +124,7 @@ def to_transfer_type( class CreateStreamResponse: stream: StreamRef | None = None created: bool | None = None + status: StreamStatus | None = None class _CreateStreamResponseTransferTypeConverter( @@ -140,6 +149,7 @@ def from_transfer_type( if value.HasField("stream") else None, created=value.created if bool(value.created) else None, + status=StreamStatus(value.status) if value.status != 0 else None, ) @typing_extensions.override @@ -154,6 +164,10 @@ def to_transfer_type( ) if value.created is not None: message.created = value.created + if value.status is not None: + message.status = streamsample.v1.stream_service_pb2.StreamStatus.ValueType( + value.status + ) return message diff --git a/advanced/samples/python/wit/stream_service/operations/create_stream.py b/advanced/samples/python/wit/stream_service/operations/create_stream.py index 4c7630fd2..9bfb27a03 100644 --- a/advanced/samples/python/wit/stream_service/operations/create_stream.py +++ b/advanced/samples/python/wit/stream_service/operations/create_stream.py @@ -36,6 +36,7 @@ async def _create_stream( stream=result.stream, ), created=result.created, + status=result.status, ) diff --git a/advanced/samples/typescript/proto/streamsample.d.ts b/advanced/samples/typescript/proto/streamsample.d.ts index e1756542f..27fc59bae 100644 --- a/advanced/samples/typescript/proto/streamsample.d.ts +++ b/advanced/samples/typescript/proto/streamsample.d.ts @@ -27,9 +27,17 @@ export namespace streamsample { retention?: google.protobuf.IDuration | null; } + enum StreamStatus { + STREAM_STATUS_UNSPECIFIED = 0, + STREAM_STATUS_OPEN = 1, + STREAM_STATUS_CLOSED = 2, + STREAM_STATUS_EXPIRED = 3, + } + interface ICreateStreamResponse { stream?: IStreamRef | null; created?: boolean | null; + status?: StreamStatus | null; } interface IAppendRecordsRequest { diff --git a/advanced/samples/typescript/wit/stream-service/models.ts b/advanced/samples/typescript/wit/stream-service/models.ts index c7736db1f..8b4262595 100644 --- a/advanced/samples/typescript/wit/stream-service/models.ts +++ b/advanced/samples/typescript/wit/stream-service/models.ts @@ -17,9 +17,17 @@ function requiredField( return value; } +export enum StreamStatus { + StreamStatusUnspecified = 0, + StreamStatusOpen = 1, + StreamStatusClosed = 2, + StreamStatusExpired = 3, +} + export interface CreateStreamResponse { stream?: StreamRef; created?: boolean; + status?: StreamStatus; } function createStreamResponseFromProto( @@ -28,6 +36,7 @@ function createStreamResponseFromProto( return { stream: proto.stream == null ? undefined : streamRefFromProto(proto.stream), created: proto.created ?? undefined, + status: proto.status == null ? undefined : (proto.status as number as StreamStatus), }; } @@ -37,6 +46,10 @@ function createStreamResponseToProto( return { stream: model.stream == null ? undefined : streamRefToProto(model.stream), created: model.created, + status: + model.status == null + ? undefined + : (model.status as number as streamsample.v1.StreamStatus), }; } @@ -450,4 +463,5 @@ export const describeStreamRequestTransferTypeConverter = { export interface CreateStreamResult { stream: Stream; created?: boolean; + status?: StreamStatus; } diff --git a/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts b/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts index 2229b2b43..bf8482e1a 100644 --- a/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts +++ b/advanced/samples/typescript/wit/stream-service/operations/create-stream.ts @@ -34,5 +34,6 @@ export async function createStream( return { stream: new Stream(requiredField(result.stream, "resource", "stream")), created: result.created, + status: result.status, }; } From ac6b7239e83f8ba90f8d753ea9cff2a2b74c3bed Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Mon, 26 Oct 2026 13:26:09 -0700 Subject: [PATCH 6/6] Dropped the extra sample, since stream-service covers a holder record. Its create-stream returns a proto-backed record that holds the stream, next to created. --- advanced/samples/README.md | 15 - .../dotnet/Nexgen.DotNetExamples.csproj | 2 +- ...exgen.DotNetProtoRecordWithResource.csproj | 22 - .../wit/proto-record-with-resource/Models.cs | 272 ------------- .../proto-record-with-resource/Operations.cs | 101 ----- .../proto-record-with-resource/Resources.cs | 36 -- .../proto-record-with-resource/Services.cs | 69 ---- .../SystemNexusWorkflowOutboundInterceptor.cs | 107 ----- .../workflowrunservice.go | 383 ------------------ advanced/samples/inputs/examples.toml | 6 - .../inputs/proto-record-with-resource.wit | 114 ------ .../proto_record_with_resource/__init__.py | 57 --- .../_resources/__init__.py | 13 - .../_resources/workflow_run.py | 63 --- .../_system_nexus_interceptor.py | 142 ------- .../wit/proto_record_with_resource/models.py | 280 ------------- .../operations/__init__.py | 3 - .../operations/start_run.py | 53 --- .../proto_record_with_resource/services.py | 24 -- .../wit/proto-record-with-resource/index.ts | 10 - .../interceptors.ts | 52 --- .../wit/proto-record-with-resource/models.ts | 245 ----------- .../operations/cancel-run.ts | 29 -- .../operations/start-run.ts | 36 -- .../proto-record-with-resource/registry.ts | 12 - .../proto-record-with-resource/resources.ts | 19 - .../proto-record-with-resource/services.ts | 33 -- xtask/src/validate.rs | 10 - 28 files changed, 1 insertion(+), 2207 deletions(-) delete mode 100644 advanced/samples/dotnet/Nexgen.DotNetProtoRecordWithResource.csproj delete mode 100644 advanced/samples/dotnet/wit/proto-record-with-resource/Models.cs delete mode 100644 advanced/samples/dotnet/wit/proto-record-with-resource/Operations.cs delete mode 100644 advanced/samples/dotnet/wit/proto-record-with-resource/Resources.cs delete mode 100644 advanced/samples/dotnet/wit/proto-record-with-resource/Services.cs delete mode 100644 advanced/samples/dotnet/wit/proto-record-with-resource/SystemNexusWorkflowOutboundInterceptor.cs delete mode 100644 advanced/samples/go/protorecordwithresource/workflowrunservice.go delete mode 100644 advanced/samples/inputs/proto-record-with-resource.wit delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/__init__.py delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/_resources/__init__.py delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/_resources/workflow_run.py delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/_system_nexus_interceptor.py delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/models.py delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/operations/__init__.py delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/operations/start_run.py delete mode 100644 advanced/samples/python/wit/proto_record_with_resource/services.py delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/index.ts delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/interceptors.ts delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/models.ts delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/operations/cancel-run.ts delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/operations/start-run.ts delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/registry.ts delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/resources.ts delete mode 100644 advanced/samples/typescript/wit/proto-record-with-resource/services.ts diff --git a/advanced/samples/README.md b/advanced/samples/README.md index be891500c..65c308241 100644 --- a/advanced/samples/README.md +++ b/advanced/samples/README.md @@ -148,21 +148,6 @@ records are proto-backed, as a stream service's are, on the sample proto in [`typescript/wit/stream-service/`](typescript/wit/stream-service/), [`typescript/tests/stream-service.test.ts`](typescript/tests/stream-service.test.ts) -### `proto-record-with-resource` - -A System Nexus operation that returns a proto-backed record that holds a -resource, next to the other fields of the same message. - -- WIT: - [`inputs/proto-record-with-resource.wit`](inputs/proto-record-with-resource.wit) -- .NET: - [`dotnet/wit/proto-record-with-resource/`](dotnet/wit/proto-record-with-resource/) -- Go: [`go/protorecordwithresource/`](go/protorecordwithresource/) -- Python: - [`python/wit/proto_record_with_resource/`](python/wit/proto_record_with_resource/) -- TypeScript: - [`typescript/wit/proto-record-with-resource/`](typescript/wit/proto-record-with-resource/) - ## Supporting files - [`inputs/deps/`](inputs/deps/): reusable Temporal semantic/common type WIT diff --git a/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj b/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj index 1f4812439..11a5e06ef 100644 --- a/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj +++ b/advanced/samples/dotnet/Nexgen.DotNetExamples.csproj @@ -14,7 +14,7 @@ - + diff --git a/advanced/samples/dotnet/Nexgen.DotNetProtoRecordWithResource.csproj b/advanced/samples/dotnet/Nexgen.DotNetProtoRecordWithResource.csproj deleted file mode 100644 index e1a2242c7..000000000 --- a/advanced/samples/dotnet/Nexgen.DotNetProtoRecordWithResource.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net8.0 - 9.0 - enable - false - Nexgen.DotNetProtoRecordWithResource - - - - - - - - - - - - - - diff --git a/advanced/samples/dotnet/wit/proto-record-with-resource/Models.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Models.cs deleted file mode 100644 index 8f31f54cf..000000000 --- a/advanced/samples/dotnet/wit/proto-record-with-resource/Models.cs +++ /dev/null @@ -1,272 +0,0 @@ -// -// Generated by nexgen latest. DO NOT EDIT! -#nullable enable - -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using Nexgen.Support; - -namespace Temporalio.Workflows -{ - - [Temporalio.Converters.TemporalTransferTypeConverter(typeof(StartWorkflowExecutionResponse.TransferTypeConverter))] - [GeneratedCode("nexgen", null)] - public record StartWorkflowExecutionResponse - { - public string? RunId { get; init; } - public bool? Started { get; init; } - - internal static StartWorkflowExecutionResponse FromTransferType(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse wire) - { - return new StartWorkflowExecutionResponse() - { - RunId = string.IsNullOrEmpty(wire.RunId) ? null : wire.RunId, - Started = wire.Started == false ? null : wire.Started, - }; - } - - internal Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse ToTransferType() - { - var proto = new Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse(); - if (RunId is { } runId) - { - proto.RunId = runId; - } - if (Started is { } started) - { - proto.Started = started; - } - return proto; - } - - /// - /// Converts this model to and from its generated transfer type. - /// - public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter - { - /// Gets the generated transfer type. - public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse); - - /// Converts a model value to its transfer type. - public object? ToTransferType(object? value) => value is null ? null : ((StartWorkflowExecutionResponse)value).ToTransferType(); - - /// Converts a transfer-type value to this model. - public object? FromTransferType(object? transferType) => transferType is null ? null : StartWorkflowExecutionResponse.FromTransferType((Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionResponse)transferType); - } - - } - - [Temporalio.Converters.TemporalTransferTypeConverter(typeof(CancelRunRequest.TransferTypeConverter))] - [GeneratedCode("nexgen", null)] - public record CancelRunRequest - { - /// - /// Initializes a new instance of the class. - /// - public CancelRunRequest(WorkflowExecution workflowExecution, string @namespace) - { - WorkflowExecution = workflowExecution; - Namespace = @namespace; - } - - public WorkflowExecution WorkflowExecution { get; init; } - public string? Reason { get; init; } - public string Namespace { get; init; } - - internal static CancelRunRequest FromTransferType(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest wire) - { - return new CancelRunRequest(WorkflowExecution.FromTransferType(wire.WorkflowExecution), wire.Namespace) - { - Reason = string.IsNullOrEmpty(wire.Reason) ? null : wire.Reason, - }; - } - - internal Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest ToTransferType() - { - var proto = new Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest(); - proto.Namespace = Namespace; - proto.WorkflowExecution = (Temporalio.Api.Common.V1.WorkflowExecution)WorkflowExecution.ToTransferType(); - if (Reason is { } reason) - { - proto.Reason = reason; - } - return proto; - } - - /// - /// Converts this model to and from its generated transfer type. - /// - public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter - { - /// Gets the generated transfer type. - public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest); - - /// Converts a model value to its transfer type. - public object? ToTransferType(object? value) => value is null ? null : ((CancelRunRequest)value).ToTransferType(); - - /// Converts a transfer-type value to this model. - public object? FromTransferType(object? transferType) => transferType is null ? null : CancelRunRequest.FromTransferType((Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionRequest)transferType); - } - - } - - [Temporalio.Converters.TemporalTransferTypeConverter(typeof(CancelRunResponse.TransferTypeConverter))] - [GeneratedCode("nexgen", null)] - public record CancelRunResponse - { - internal static CancelRunResponse FromTransferType(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse wire) - { - return new CancelRunResponse(); - } - - internal Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse ToTransferType() - { - var proto = new Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse(); - return proto; - } - - /// - /// Converts this model to and from its generated transfer type. - /// - public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter - { - /// Gets the generated transfer type. - public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse); - - /// Converts a model value to its transfer type. - public object? ToTransferType(object? value) => value is null ? null : ((CancelRunResponse)value).ToTransferType(); - - /// Converts a transfer-type value to this model. - public object? FromTransferType(object? transferType) => transferType is null ? null : CancelRunResponse.FromTransferType((Temporalio.Api.WorkflowService.V1.RequestCancelWorkflowExecutionResponse)transferType); - } - - } - - [Temporalio.Converters.TemporalTransferTypeConverter(typeof(StartRunRequest.TransferTypeConverter))] - [GeneratedCode("nexgen", null)] - public record StartRunRequest - { - /// - /// Initializes a new instance of the class. - /// - public StartRunRequest(string workflow, string workflowId, string taskQueue, string @namespace) - { - Workflow = workflow; - WorkflowId = workflowId; - TaskQueue = taskQueue; - Namespace = @namespace; - } - - public string Workflow { get; init; } - public string WorkflowId { get; init; } - public string TaskQueue { get; init; } - public System.TimeSpan? WorkflowStartDelay { get; init; } - public string Namespace { get; init; } - - internal static StartRunRequest FromTransferType(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest wire) - { - return new StartRunRequest(Nexgen.Support.ProtoExtensions.FromWorkflowTypeProto(wire.WorkflowType), wire.WorkflowId, Nexgen.Support.ProtoExtensions.FromTaskQueueProto(wire.TaskQueue), wire.Namespace) - { - WorkflowStartDelay = wire.WorkflowStartDelay == null ? null : Nexgen.Support.ProtoExtensions.FromDurationProto(wire.WorkflowStartDelay), - }; - } - - internal Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest ToTransferType() - { - var proto = new Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest(); - proto.Namespace = Namespace; - proto.WorkflowType = Nexgen.Support.ProtoExtensions.ToWorkflowTypeProto(Workflow); - proto.WorkflowId = WorkflowId; - proto.TaskQueue = Nexgen.Support.ProtoExtensions.ToTaskQueueProto(TaskQueue); - if (WorkflowStartDelay is { } workflowStartDelay) - { - proto.WorkflowStartDelay = workflowStartDelay.ToProto(); - } - return proto; - } - - /// - /// Converts this model to and from its generated transfer type. - /// - public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter - { - /// Gets the generated transfer type. - public System.Type TransferType => typeof(Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest); - - /// Converts a model value to its transfer type. - public object? ToTransferType(object? value) => value is null ? null : ((StartRunRequest)value).ToTransferType(); - - /// Converts a transfer-type value to this model. - public object? FromTransferType(object? transferType) => transferType is null ? null : StartRunRequest.FromTransferType((Temporalio.Api.WorkflowService.V1.StartWorkflowExecutionRequest)transferType); - } - - } - - [GeneratedCode("nexgen", null)] - public record StartRunResult - { - /// - /// Initializes a new instance of the class. - /// - public StartRunResult(WorkflowRun run) - { - Run = run; - } - - public WorkflowRun Run { get; init; } - public bool? Started { get; init; } - } - - [Temporalio.Converters.TemporalTransferTypeConverter(typeof(WorkflowExecution.TransferTypeConverter))] - [GeneratedCode("nexgen", null)] - public record WorkflowExecution - { - /// - /// Initializes a new instance of the class. - /// - public WorkflowExecution(string workflowId) - { - WorkflowId = workflowId; - } - - public string WorkflowId { get; init; } - public string? RunId { get; init; } - - internal static WorkflowExecution FromTransferType(Temporalio.Api.Common.V1.WorkflowExecution wire) - { - return new WorkflowExecution(wire.WorkflowId) - { - RunId = string.IsNullOrEmpty(wire.RunId) ? null : wire.RunId, - }; - } - - internal Temporalio.Api.Common.V1.WorkflowExecution ToTransferType() - { - var proto = new Temporalio.Api.Common.V1.WorkflowExecution(); - proto.WorkflowId = WorkflowId; - if (RunId is { } runId) - { - proto.RunId = runId; - } - return proto; - } - - /// - /// Converts this model to and from its generated transfer type. - /// - public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTransferTypeConverter - { - /// Gets the generated transfer type. - public System.Type TransferType => typeof(Temporalio.Api.Common.V1.WorkflowExecution); - - /// Converts a model value to its transfer type. - public object? ToTransferType(object? value) => value is null ? null : ((WorkflowExecution)value).ToTransferType(); - - /// Converts a transfer-type value to this model. - public object? FromTransferType(object? transferType) => transferType is null ? null : WorkflowExecution.FromTransferType((Temporalio.Api.Common.V1.WorkflowExecution)transferType); - } - - } - -} diff --git a/advanced/samples/dotnet/wit/proto-record-with-resource/Operations.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Operations.cs deleted file mode 100644 index eb89a9048..000000000 --- a/advanced/samples/dotnet/wit/proto-record-with-resource/Operations.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// Generated by nexgen latest. DO NOT EDIT! -#nullable enable - -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Google.Protobuf.WellKnownTypes; -using Temporalio.Workflows; -using Nexgen.Support; - -namespace Temporalio.Workflows -{ - - [GeneratedCode("nexgen", null)] - public class StartRunOptions - { - /// - /// Initializes a new instance of the class. - /// - public StartRunOptions(string workflow, string workflowId, string taskQueue) - { - Workflow = workflow; - WorkflowId = workflowId; - TaskQueue = taskQueue; - } - - public string Workflow { get; set; } - public string WorkflowId { get; set; } - public string TaskQueue { get; set; } - public System.TimeSpan? WorkflowStartDelay { get; set; } - } - - [GeneratedCode("nexgen", null)] - public class CancelRunOptions - { - /// - /// Initializes a new instance of the class. - /// - public CancelRunOptions(WorkflowExecution workflowExecution) - { - WorkflowExecution = workflowExecution; - } - - public WorkflowExecution WorkflowExecution { get; set; } - public string? Reason { get; set; } - } - - /// - /// Provides generated workflow operation helpers. - /// - [GeneratedCode("nexgen", null)] - public static class Operations - { - [GeneratedCode("nexgen", null)] - private const string WorkflowRunServiceEndpoint = "temporal-system"; - - [GeneratedCode("nexgen", null)] - private static async Task StartRunAsync(StartRunRequest request) - { - var client = Workflow.CreateNexusWorkflowClient(WorkflowRunServiceEndpoint); - var result = await client.ExecuteNexusOperationAsync(svc => svc.StartRun(request)).ConfigureAwait(true); - return new StartRunResult(new WorkflowRun(request.WorkflowId, string.IsNullOrEmpty(result.RunId) ? null : result.RunId)) { Started = result.Started }; - } - - /// Options for the operation. - [GeneratedCode("nexgen", null)] - public static Task StartRunAsync(StartRunOptions options) - { - var request = new StartRunRequest(options.Workflow, options.WorkflowId, options.TaskQueue, Nexgen.Support.TemporalWorkflowContext.WorkflowNamespace()) - { - WorkflowStartDelay = options.WorkflowStartDelay, - }; - return StartRunAsync(request); - } - - [GeneratedCode("nexgen", null)] - private static async Task CancelRunAsync(CancelRunRequest request) - { - var client = Workflow.CreateNexusWorkflowClient(WorkflowRunServiceEndpoint); - var result = await client.ExecuteNexusOperationAsync(svc => svc.CancelRun(request)).ConfigureAwait(true); - return result; - } - - /// Options for the operation. - [GeneratedCode("nexgen", null)] - public static Task CancelRunAsync(CancelRunOptions options) - { - var request = new CancelRunRequest(options.WorkflowExecution, Nexgen.Support.TemporalWorkflowContext.WorkflowNamespace()) - { - Reason = options.Reason, - }; - return CancelRunAsync(request); - } - - } - -} diff --git a/advanced/samples/dotnet/wit/proto-record-with-resource/Resources.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Resources.cs deleted file mode 100644 index 385a9cc3b..000000000 --- a/advanced/samples/dotnet/wit/proto-record-with-resource/Resources.cs +++ /dev/null @@ -1,36 +0,0 @@ -// -// Generated by nexgen latest. DO NOT EDIT! -#nullable enable - -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Threading.Tasks; -using Temporalio.Workflows; -using Nexgen.Support; - -namespace Temporalio.Workflows -{ - - [GeneratedCode("nexgen", null)] - public class WorkflowRun - { - public WorkflowRun(string workflowId, string? runId) - { - WorkflowId = workflowId; - RunId = runId; - } - - public string WorkflowId { get; } - public string? RunId { get; } - - [GeneratedCode("nexgen", null)] - public Task CancelAsync(string? reason) - { - var request = new CancelRunOptions(new WorkflowExecution(WorkflowId) { RunId = RunId }) { Reason = reason }; - return Operations.CancelRunAsync(request); - } - - } - -} diff --git a/advanced/samples/dotnet/wit/proto-record-with-resource/Services.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/Services.cs deleted file mode 100644 index e125ea00a..000000000 --- a/advanced/samples/dotnet/wit/proto-record-with-resource/Services.cs +++ /dev/null @@ -1,69 +0,0 @@ -// -// Generated by nexgen latest. DO NOT EDIT! -#nullable enable - -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Threading.Tasks; -using NexusRpc; -using Temporalio.Converters; -using Nexgen.Support; - -namespace Temporalio.Workflows -{ - - [GeneratedCode("nexgen", null)] - [NexusService("temporal.example.v1.WorkflowRunService")] - internal interface IWorkflowRunService - { - [GeneratedCode("nexgen", null)] - [NexusOperation("StartRun")] - StartWorkflowExecutionResponse StartRun(StartRunRequest request); - - [GeneratedCode("nexgen", null)] - [NexusOperation("CancelRun")] - CancelRunResponse CancelRun(CancelRunRequest request); - - } - - internal interface INexgenOperationInfo - { - OperationDefinition Operation { get; } - - Func? SerializationContext { get; } - } - - internal sealed class NexgenOperationInfo : INexgenOperationInfo - { - internal NexgenOperationInfo( - OperationDefinition operation, - Func? serializationContext = null) - { - Operation = operation; - SerializationContext = serializationContext; - } - - internal OperationDefinition Operation { get; } - - OperationDefinition INexgenOperationInfo.Operation => Operation; - - internal Func? SerializationContext { get; } - - Func? INexgenOperationInfo.SerializationContext => SerializationContext == null ? null : request => SerializationContext((TRequest)request); - } - - internal static class NexgenOperationRegistry - { - private static readonly ServiceDefinition WorkflowRunServiceServiceDefinition = ServiceDefinition.FromType(); - - internal static IReadOnlyDictionary<(string Service, string Operation), INexgenOperationInfo> Operations { get; } = new Dictionary<(string Service, string Operation), INexgenOperationInfo> - { - [("temporal.example.v1.WorkflowRunService", "StartRun")] = new NexgenOperationInfo( - WorkflowRunServiceServiceDefinition.Operations["StartRun"]), - [("temporal.example.v1.WorkflowRunService", "CancelRun")] = new NexgenOperationInfo( - WorkflowRunServiceServiceDefinition.Operations["CancelRun"]), - }; - } - -} diff --git a/advanced/samples/dotnet/wit/proto-record-with-resource/SystemNexusWorkflowOutboundInterceptor.cs b/advanced/samples/dotnet/wit/proto-record-with-resource/SystemNexusWorkflowOutboundInterceptor.cs deleted file mode 100644 index 34588a30d..000000000 --- a/advanced/samples/dotnet/wit/proto-record-with-resource/SystemNexusWorkflowOutboundInterceptor.cs +++ /dev/null @@ -1,107 +0,0 @@ -// -// Generated by nexgen latest. DO NOT EDIT! -#nullable enable - -using System.CodeDom.Compiler; -using System.Threading.Tasks; -using Temporalio.Workflows; - -namespace Temporalio.Worker.Interceptors -{ - - public partial class WorkflowOutboundInterceptor - { - /// - /// Intercept the StartRun operation. - /// - /// Request for the operation. - /// Operation handle. - [GeneratedCode("nexgen", null)] - public virtual Task> StartRunAsync(StartRunRequest request) => Next.StartRunAsync(request); - - /// - /// Intercept the CancelRun operation. - /// - /// Request for the operation. - /// Operation handle. - [GeneratedCode("nexgen", null)] - public virtual Task> CancelRunAsync(CancelRunRequest request) => Next.CancelRunAsync(request); - - } - -} - -namespace Temporalio.Worker -{ - - internal partial class WorkflowInstance - { - [GeneratedCode("nexgen", null)] - private Task> StartSystemNexusOperationAsync( - string service, - string operationName, - object? arg) - { - if (service == "temporal.example.v1.WorkflowRunService" && - operationName == "StartRun") - { - if (arg is not StartRunRequest request) - { - throw new System.ArgumentException("System Nexus operation expects a StartRunRequest request.", nameof(arg)); - } - if (typeof(TResult) != typeof(StartWorkflowExecutionResponse)) - { - throw new System.ArgumentException("System Nexus operation expects a StartWorkflowExecutionResponse result."); - } - var handle = outbound.Value.StartRunAsync(request); - return (Task>)(object)handle; - } - if (service == "temporal.example.v1.WorkflowRunService" && - operationName == "CancelRun") - { - if (arg is not CancelRunRequest request) - { - throw new System.ArgumentException("System Nexus operation expects a CancelRunRequest request.", nameof(arg)); - } - if (typeof(TResult) != typeof(CancelRunResponse)) - { - throw new System.ArgumentException("System Nexus operation expects a CancelRunResponse result."); - } - var handle = outbound.Value.CancelRunAsync(request); - return (Task>)(object)handle; - } - return outbound.Value.ScheduleSystemNexusOperationAsync(new( - Service: service, - Operation: new( - operationName, - arg?.GetType() ?? typeof(object), - typeof(TResult)), - Arg: arg, - Headers: null)); - } - - internal partial class OutboundImpl - { - [GeneratedCode("nexgen", null)] - public override Task> StartRunAsync(StartRunRequest request) => instance.outbound.Value.ScheduleSystemNexusOperationAsync(new( - Service: "temporal.example.v1.WorkflowRunService", - Operation: new( - "StartRun", - typeof(StartRunRequest), - typeof(StartWorkflowExecutionResponse)), - Arg: request, - Headers: null)); - - [GeneratedCode("nexgen", null)] - public override Task> CancelRunAsync(CancelRunRequest request) => instance.outbound.Value.ScheduleSystemNexusOperationAsync(new( - Service: "temporal.example.v1.WorkflowRunService", - Operation: new( - "CancelRun", - typeof(CancelRunRequest), - typeof(CancelRunResponse)), - Arg: request, - Headers: null)); - - } - } -} diff --git a/advanced/samples/go/protorecordwithresource/workflowrunservice.go b/advanced/samples/go/protorecordwithresource/workflowrunservice.go deleted file mode 100644 index a1fe80417..000000000 --- a/advanced/samples/go/protorecordwithresource/workflowrunservice.go +++ /dev/null @@ -1,383 +0,0 @@ -// Code generated by nexgen latest. DO NOT EDIT. - -package protorecordwithresource - -import ( - "time" - - common "go.temporal.io/api/common/v1" - workflowservice "go.temporal.io/api/workflowservice/v1" - support "go.temporal.io/sdk/advanced/samples/go/systemsupport" - "go.temporal.io/sdk/converter" - "go.temporal.io/sdk/internal" - commonconverter "go.temporal.io/sdk/internal/common/converter" - "go.temporal.io/sdk/workflow" -) - -// --- Datatypes --- - -type startRunRequest struct { - Workflow string - WorkflowID string - TaskQueue string - WorkflowStartDelay *time.Duration - Namespace string -} - -func (m startRunRequest) GetWorkflow() string { - return m.Workflow -} - -func (m startRunRequest) GetWorkflowID() string { - return m.WorkflowID -} - -func (m startRunRequest) GetTaskQueue() string { - return m.TaskQueue -} - -func (m startRunRequest) GetWorkflowStartDelay() *time.Duration { - return m.WorkflowStartDelay -} - -func (m startRunRequest) GetNamespace() string { - return m.Namespace -} - -func startRunRequestToProto(cfg *commonconverter.SystemNexusConfig, m *startRunRequest) (*workflowservice.StartWorkflowExecutionRequest, error) { - message := &workflowservice.StartWorkflowExecutionRequest{} - { - converted, err := support.WorkflowTypeToProto(cfg.DataConverter, &m.Workflow) - if err != nil { - return nil, err - } - message.WorkflowType = converted - } - message.WorkflowId = m.WorkflowID - { - converted, err := support.TaskQueueToProto(cfg.DataConverter, &m.TaskQueue) - if err != nil { - return nil, err - } - message.TaskQueue = converted - } - { - converted, err := support.DurationToProto(cfg.DataConverter, m.WorkflowStartDelay) - if err != nil { - return nil, err - } - message.WorkflowStartDelay = converted - } - message.Namespace = m.Namespace - return message, nil -} - -func startRunRequestFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.StartWorkflowExecutionRequest, out *startRunRequest) error { - *out = startRunRequest{} - { - converted, err := support.WorkflowTypeFromProto(cfg.DataConverter, proto.GetWorkflowType()) - if err != nil { - return err - } - if converted != nil { - out.Workflow = *converted - } - } - out.WorkflowID = proto.GetWorkflowId() - { - converted, err := support.TaskQueueFromProto(cfg.DataConverter, proto.GetTaskQueue()) - if err != nil { - return err - } - if converted != nil { - out.TaskQueue = *converted - } - } - { - converted, err := support.DurationFromProto(cfg.DataConverter, proto.GetWorkflowStartDelay()) - if err != nil { - return err - } - out.WorkflowStartDelay = converted - } - out.Namespace = proto.GetNamespace() - return nil -} - -func (startRunRequest) TransferTypeConverter() (converter.TransferTypeConverter, error) { - return commonconverter.NewSystemNexusTransferTypeConverter( - startRunRequestToProto, - startRunRequestFromProto, - ) -} - -type cancelRunRequest struct { - WorkflowExecution WorkflowExecution - Reason *string - Namespace string -} - -func (m cancelRunRequest) GetWorkflowExecution() WorkflowExecution { - return m.WorkflowExecution -} - -func (m cancelRunRequest) GetReason() *string { - return m.Reason -} - -func (m cancelRunRequest) GetNamespace() string { - return m.Namespace -} - -func cancelRunRequestToProto(cfg *commonconverter.SystemNexusConfig, m *cancelRunRequest) (*workflowservice.RequestCancelWorkflowExecutionRequest, error) { - message := &workflowservice.RequestCancelWorkflowExecutionRequest{} - { - converted, err := workflowExecutionToProto(cfg, &m.WorkflowExecution) - if err != nil { - return nil, err - } - message.WorkflowExecution = converted - } - if m.Reason != nil { - message.Reason = (*m.Reason) - } - message.Namespace = m.Namespace - return message, nil -} - -func cancelRunRequestFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.RequestCancelWorkflowExecutionRequest, out *cancelRunRequest) error { - *out = cancelRunRequest{} - if err := workflowExecutionFromProto(cfg, proto.GetWorkflowExecution(), &out.WorkflowExecution); err != nil { - return err - } - { - converted := proto.GetReason() - out.Reason = &converted - } - out.Namespace = proto.GetNamespace() - return nil -} - -func (cancelRunRequest) TransferTypeConverter() (converter.TransferTypeConverter, error) { - return commonconverter.NewSystemNexusTransferTypeConverter( - cancelRunRequestToProto, - cancelRunRequestFromProto, - ) -} - -type startWorkflowExecutionResponse struct { - RunID *string - Started *bool -} - -func (m startWorkflowExecutionResponse) GetRunID() *string { - return m.RunID -} - -func (m startWorkflowExecutionResponse) GetStarted() *bool { - return m.Started -} - -func startWorkflowExecutionResponseToProto(cfg *commonconverter.SystemNexusConfig, m *startWorkflowExecutionResponse) (*workflowservice.StartWorkflowExecutionResponse, error) { - message := &workflowservice.StartWorkflowExecutionResponse{} - if m.RunID != nil { - message.RunId = (*m.RunID) - } - if m.Started != nil { - message.Started = (*m.Started) - } - return message, nil -} - -func startWorkflowExecutionResponseFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.StartWorkflowExecutionResponse, out *startWorkflowExecutionResponse) error { - *out = startWorkflowExecutionResponse{} - { - converted := proto.GetRunId() - out.RunID = &converted - } - { - converted := proto.GetStarted() - out.Started = &converted - } - return nil -} - -// --- Resources --- - -type WorkflowRun struct { - // WorkflowID - Required. - WorkflowID string - // RunID - Optional. - RunID *string -} - -func NewWorkflowRun(workflowID string, runID string) WorkflowRun { - var runIDPtr *string - if runID != "" { - runIDPtr = &runID - } - return WorkflowRun{ - WorkflowID: workflowID, - RunID: runIDPtr, - } -} - -func (u *WorkflowRun) Cancel(ctx workflow.Context, reason string) workflow.Future { - var reasonPtr *string - if reason != "" { - reasonPtr = &reason - } - return cancelRun(ctx, cancelRunRequest{WorkflowExecution: WorkflowExecution{WorkflowID: u.WorkflowID, RunID: u.RunID}, Reason: reasonPtr}) -} - -// --- Operations (internal) --- - -func startRun(ctx workflow.Context, request startRunRequest) workflow.Future { - request.Namespace = support.WorkflowNamespace(ctx) - c := internal.NewSystemNexusClient("temporal.example.v1.WorkflowRunService") - fut := c.ExecuteOperation(ctx, "StartRun", request, workflow.NexusOperationOptions{}) - result, resultSettable := workflow.NewFuture(ctx) - workflow.Go(ctx, func(ctx workflow.Context) { - var result workflowservice.StartWorkflowExecutionResponse - if err := fut.Get(ctx, &result); err != nil { - resultSettable.SetError(err) - return - } - var heldStarted *bool - if value := result.GetStarted(); value != false { - heldStarted = &value - } - value := StartRunResult{Run: NewWorkflowRun(request.WorkflowID, result.GetRunId()), Started: heldStarted} - resultSettable.Set(value, nil) - }) - return result -} - -func cancelRun(ctx workflow.Context, request cancelRunRequest) workflow.Future { - request.Namespace = support.WorkflowNamespace(ctx) - c := internal.NewSystemNexusClient("temporal.example.v1.WorkflowRunService") - fut := c.ExecuteOperation(ctx, "CancelRun", request, workflow.NexusOperationOptions{}) - result, resultSettable := workflow.NewFuture(ctx) - workflow.Go(ctx, func(ctx workflow.Context) { - var value CancelRunResponse - if err := fut.Get(ctx, &value); err != nil { - resultSettable.SetError(err) - return - } - resultSettable.Set(value, nil) - }) - return result -} - -// --- Operations (public API) --- - -type WorkflowExecution struct { - // WorkflowID - Required. - WorkflowID string - // RunID - Optional. - RunID *string -} - -func (m WorkflowExecution) GetWorkflowID() string { - return m.WorkflowID -} - -func (m WorkflowExecution) GetRunID() *string { - return m.RunID -} - -func workflowExecutionToProto(cfg *commonconverter.SystemNexusConfig, m *WorkflowExecution) (*common.WorkflowExecution, error) { - message := &common.WorkflowExecution{} - message.WorkflowId = m.WorkflowID - if m.RunID != nil { - message.RunId = (*m.RunID) - } - return message, nil -} - -func workflowExecutionFromProto(cfg *commonconverter.SystemNexusConfig, proto *common.WorkflowExecution, out *WorkflowExecution) error { - *out = WorkflowExecution{} - out.WorkflowID = proto.GetWorkflowId() - { - converted := proto.GetRunId() - out.RunID = &converted - } - return nil -} - -type CancelRunResponse struct { -} - -func cancelRunResponseToProto(cfg *commonconverter.SystemNexusConfig, m *CancelRunResponse) (*workflowservice.RequestCancelWorkflowExecutionResponse, error) { - message := &workflowservice.RequestCancelWorkflowExecutionResponse{} - return message, nil -} - -func cancelRunResponseFromProto(cfg *commonconverter.SystemNexusConfig, proto *workflowservice.RequestCancelWorkflowExecutionResponse, out *CancelRunResponse) error { - *out = CancelRunResponse{} - return nil -} - -func (CancelRunResponse) TransferTypeConverter() (converter.TransferTypeConverter, error) { - return commonconverter.NewSystemNexusTransferTypeConverter( - cancelRunResponseToProto, - cancelRunResponseFromProto, - ) -} - -type StartRunResult struct { - // Run - Required. - Run WorkflowRun - // Started - Optional. - Started *bool -} - -func (m StartRunResult) GetRun() WorkflowRun { - return m.Run -} - -func (m StartRunResult) GetStarted() *bool { - return m.Started -} - -type StartRunOptions struct { - // Workflow - Required. - Workflow string - // WorkflowID - Required. - WorkflowID string - // TaskQueue - Required. - TaskQueue string - // WorkflowStartDelay - Optional. - WorkflowStartDelay time.Duration -} - -func StartRun(ctx workflow.Context, opts StartRunOptions) workflow.Future { - var workflowStartDelay *time.Duration - if opts.WorkflowStartDelay != 0 { - workflowStartDelay = &opts.WorkflowStartDelay - } - return startRun(ctx, startRunRequest{ - Workflow: opts.Workflow, - WorkflowID: opts.WorkflowID, - TaskQueue: opts.TaskQueue, - WorkflowStartDelay: workflowStartDelay, - }) -} - -type CancelRunOptions struct { - // WorkflowExecution - Required. - WorkflowExecution WorkflowExecution - // Reason - Optional. - Reason string -} - -func CancelRun(ctx workflow.Context, opts CancelRunOptions) workflow.Future { - var reason *string - if opts.Reason != "" { - reason = &opts.Reason - } - return cancelRun(ctx, cancelRunRequest{ - WorkflowExecution: opts.WorkflowExecution, - Reason: reason, - }) -} diff --git a/advanced/samples/inputs/examples.toml b/advanced/samples/inputs/examples.toml index 6630ad9cf..a41cf5a86 100644 --- a/advanced/samples/inputs/examples.toml +++ b/advanced/samples/inputs/examples.toml @@ -26,12 +26,6 @@ descriptors = "temporal" languages = ["python", "dotnet", "typescript"] descriptors = "temporal" -# A proto-backed result record that holds a resource next to its other fields. -[proto-record-with-resource] -system_nexus = true -languages = ["python", "typescript", "dotnet", "go"] -descriptors = "temporal" - [stream-service] languages = ["python", "typescript", "dotnet"] # The sample proto imports the Temporal API messages, so it needs both sets. diff --git a/advanced/samples/inputs/proto-record-with-resource.wit b/advanced/samples/inputs/proto-record-with-resource.wit deleted file mode 100644 index cded4e3ab..000000000 --- a/advanced/samples/inputs/proto-record-with-resource.wit +++ /dev/null @@ -1,114 +0,0 @@ -package temporal:nexus@1.0.0; - -world system { - export workflow-run-service; -} - -/// @nexus.endpoint "temporal-system" go="__temporal_system" python="__temporal_system" -/// @nexus.service-name "temporal.example.v1.WorkflowRunService" -interface workflow-run-service { - use nexus:temporal-types/model@1.0.0.{duration, placeholder, task-queue, workflow-type}; - - /// @nexus.proto "temporal.api.common.v1.WorkflowExecution" typescript-import="@temporalio/proto" - record workflow-execution { - workflow-id: string, - run-id: option, - } - - resource workflow-run { - constructor(workflow-id: string, run-id: option); - - /// @nexus.operation "cancel-run" - cancel: func(reason: option); - } - - /// @nexus.proto "temporal.api.workflowservice.v1.StartWorkflowExecutionRequest" typescript-import="@temporalio/proto" - record start-run-request { - /// @nexus.proto-field "workflow_type" - workflow: workflow-type, - /// @nexus.omit - input: placeholder, - workflow-id: string, - task-queue: task-queue, - workflow-start-delay: option, - /// @nexus.source python="workflow_namespace()" typescript="workflowNamespace()" go="WorkflowNamespace(ctx)" dotnet="TemporalWorkflowContext.WorkflowNamespace()" - namespace: string, - /// @nexus.omit - workflow-execution-timeout: placeholder, - /// @nexus.omit - workflow-run-timeout: placeholder, - /// @nexus.omit - workflow-task-timeout: placeholder, - /// @nexus.omit - identity: placeholder, - /// @nexus.omit - request-id: placeholder, - /// @nexus.omit - workflow-id-reuse-policy: placeholder, - /// @nexus.omit - workflow-id-conflict-policy: placeholder, - /// @nexus.omit - retry-policy: placeholder, - /// @nexus.omit - cron-schedule: placeholder, - /// @nexus.omit - memo: placeholder, - /// @nexus.omit - search-attributes: placeholder, - /// @nexus.omit - header: placeholder, - /// @nexus.omit - request-eager-execution: placeholder, - /// @nexus.omit - continued-failure: placeholder, - /// @nexus.omit - last-completion-result: placeholder, - /// @nexus.omit - completion-callbacks: placeholder, - /// @nexus.omit - user-metadata: placeholder, - /// @nexus.omit - links: placeholder, - /// @nexus.omit - versioning-override: placeholder, - /// @nexus.omit - on-conflict-options: placeholder, - /// @nexus.omit - priority: placeholder, - /// @nexus.omit - eager-worker-deployment-options: placeholder, - /// @nexus.omit - time-skipping-config: placeholder, - } - - // The handle binds from the response, and the other fields stay beside it. - /// @nexus.proto "temporal.api.workflowservice.v1.StartWorkflowExecutionResponse" typescript-import="@temporalio/proto" - record start-run-result { - run: own, - started: option, - } - - /// @nexus.proto "temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest" typescript-import="@temporalio/proto" - record cancel-run-request { - /// @nexus.source python="workflow_namespace()" typescript="workflowNamespace()" go="WorkflowNamespace(ctx)" dotnet="TemporalWorkflowContext.WorkflowNamespace()" - namespace: string, - workflow-execution: workflow-execution, - reason: option, - /// @nexus.omit - identity: placeholder, - /// @nexus.omit - request-id: placeholder, - /// @nexus.omit - first-execution-run-id: placeholder, - /// @nexus.omit - links: placeholder, - } - - /// @nexus.proto "temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionResponse" typescript-import="@temporalio/proto" - record cancel-run-response { - } - - start-run: func(request: start-run-request) -> start-run-result; - - cancel-run: func(request: cancel-run-request) -> cancel-run-response; -} diff --git a/advanced/samples/python/wit/proto_record_with_resource/__init__.py b/advanced/samples/python/wit/proto_record_with_resource/__init__.py deleted file mode 100644 index 6b5261274..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations - -import collections.abc -import typing - -import nexusrpc -import temporalio.converter -from . import services as _services -from .operations.start_run import start_run -from ._resources.workflow_run import cancel_run - -__all__ = [ - "cancel_run", - "start_run", -] - - -_InputT = typing.TypeVar("_InputT") -_OutputT = typing.TypeVar("_OutputT") - - -_SerializationContextFactory = collections.abc.Callable[ - [_InputT], temporalio.converter.SerializationContext -] - - -class _NexusOperationInfo(typing.Generic[_InputT, _OutputT]): - def __init__( - self, - *, - operation: nexusrpc.Operation[_InputT, _OutputT], - serialization_context: _SerializationContextFactory[_InputT] | None = None, - ) -> None: - self.operation: nexusrpc.Operation[_InputT, _OutputT] = operation - self.serialization_context: _SerializationContextFactory[_InputT] | None = ( - serialization_context - ) - - -__nexus_operation_registry__: dict[ - tuple[str, str], _NexusOperationInfo[typing.Any, typing.Any] -] = { - ( - "temporal.example.v1.WorkflowRunService", - "StartRun", - ): _NexusOperationInfo( - operation=_services.WorkflowRunService.start_run, - ), - ( - "temporal.example.v1.WorkflowRunService", - "CancelRun", - ): _NexusOperationInfo( - operation=_services.WorkflowRunService.cancel_run, - ), -} diff --git a/advanced/samples/python/wit/proto_record_with_resource/_resources/__init__.py b/advanced/samples/python/wit/proto_record_with_resource/_resources/__init__.py deleted file mode 100644 index c198b2218..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/_resources/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations - -from .workflow_run import ( - WorkflowRun, - StartRunResult, -) - -__all__ = [ - "WorkflowRun", - "StartRunResult", -] diff --git a/advanced/samples/python/wit/proto_record_with_resource/_resources/workflow_run.py b/advanced/samples/python/wit/proto_record_with_resource/_resources/workflow_run.py deleted file mode 100644 index cff895a2f..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/_resources/workflow_run.py +++ /dev/null @@ -1,63 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations - -import dataclasses -import temporalio.workflow - -from ..models import ( - CancelRunRequest, - CancelRunResponse, - WorkflowExecution, -) - - -@dataclasses.dataclass -class WorkflowRun: - workflow_id: str - run_id: str | None - - async def cancel( - self, - reason: str | None = None, - ) -> None: - request = CancelRunRequest( - workflow_execution=WorkflowExecution( - workflow_id=self.workflow_id, run_id=self.run_id - ), - reason=reason, - ) - handle = await _cancel_run(request) - await handle - - -@dataclasses.dataclass(slots=True) -class StartRunResult: - run: WorkflowRun - started: bool | None = None - - -async def _cancel_run( - request: CancelRunRequest, -) -> temporalio.workflow.NexusOperationHandle[CancelRunResponse,]: - nexus_client = temporalio.workflow.create_nexus_client( - service="temporal.example.v1.WorkflowRunService", - endpoint="__temporal_system", - ) - return await nexus_client.start_operation( - operation="CancelRun", - input=request, - output_type=CancelRunResponse, - ) - - -async def cancel_run( - *, - workflow_execution: WorkflowExecution, - reason: str | None = None, -) -> temporalio.workflow.NexusOperationHandle[CancelRunResponse,]: - request = CancelRunRequest( - workflow_execution=workflow_execution, - reason=reason, - ) - return await _cancel_run(request) diff --git a/advanced/samples/python/wit/proto_record_with_resource/_system_nexus_interceptor.py b/advanced/samples/python/wit/proto_record_with_resource/_system_nexus_interceptor.py deleted file mode 100644 index 9829e2626..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/_system_nexus_interceptor.py +++ /dev/null @@ -1,142 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations - - -import abc -import typing - -from . import models - -if typing.TYPE_CHECKING: - import temporalio.workflow - from temporalio.worker._interceptor import StartNexusOperationInput - - -__all__ = [ - "_start_system_nexus_operation", - "_SystemNexusWorkflowOutboundInterceptorBase", - "_SystemNexusWorkflowOutboundInterceptorTerminal", -] - - -_InputT = typing.TypeVar("_InputT") -_OutputT = typing.TypeVar("_OutputT") - - -async def _start_system_nexus_operation( - interceptor: _SystemNexusWorkflowOutboundInterceptorBase, - input: StartNexusOperationInput[_InputT, _OutputT], -) -> temporalio.workflow.NexusOperationHandle[_OutputT]: - if ( - input.service == "temporal.example.v1.WorkflowRunService" - and input.operation_name == "StartRun" - ): - typed_input = typing.cast( - "StartNexusOperationInput[models.StartRunRequest, models.StartWorkflowExecutionResponse]", - input, - ) - # The dispatch check above establishes that this operation's response type is _OutputT. - return typing.cast( - "temporalio.workflow.NexusOperationHandle[_OutputT]", - await interceptor.start_start_run(typed_input.input), - ) - if ( - input.service == "temporal.example.v1.WorkflowRunService" - and input.operation_name == "CancelRun" - ): - typed_input = typing.cast( - "StartNexusOperationInput[models.CancelRunRequest, models.CancelRunResponse]", - input, - ) - # The dispatch check above establishes that this operation's response type is _OutputT. - return typing.cast( - "temporalio.workflow.NexusOperationHandle[_OutputT]", - await interceptor.start_cancel_run(typed_input.input), - ) - return await interceptor.start_system_nexus_operation(input) - - -class _SystemNexusWorkflowOutboundInterceptorBase(abc.ABC): - @abc.abstractmethod - def _next_system_nexus_interceptor( - self, - ) -> _SystemNexusWorkflowOutboundInterceptorBase: ... - - @abc.abstractmethod - async def start_system_nexus_operation( - self, - input: StartNexusOperationInput[_InputT, _OutputT], - ) -> temporalio.workflow.NexusOperationHandle[_OutputT]: - """Intercept a System Nexus operation through the generic hook.""" - ... - - async def start_start_run( - self, request: models.StartRunRequest - ) -> temporalio.workflow.NexusOperationHandle[ - models.StartWorkflowExecutionResponse - ]: - """Intercept the StartRun operation.""" - return await self._next_system_nexus_interceptor().start_start_run(request) - - async def start_cancel_run( - self, request: models.CancelRunRequest - ) -> temporalio.workflow.NexusOperationHandle[models.CancelRunResponse]: - """Intercept the CancelRun operation.""" - return await self._next_system_nexus_interceptor().start_cancel_run(request) - - -class _SystemNexusWorkflowOutboundInterceptorTerminal(abc.ABC): - @abc.abstractmethod - async def _intercept_system_nexus_operation( - self, - input: StartNexusOperationInput[_InputT, _OutputT], - ) -> temporalio.workflow.NexusOperationHandle[_OutputT]: ... - - async def start_start_run( - self, request: models.StartRunRequest - ) -> temporalio.workflow.NexusOperationHandle[ - models.StartWorkflowExecutionResponse - ]: - from temporalio.worker._interceptor import StartNexusOperationInput - from temporalio.nexus.system import TEMPORAL_SYSTEM_ENDPOINT - from temporalio.workflow import NexusOperationCancellationType - - return await self._intercept_system_nexus_operation( - StartNexusOperationInput( - endpoint=TEMPORAL_SYSTEM_ENDPOINT, - service="temporal.example.v1.WorkflowRunService", - operation="StartRun", - input=request, - output_type=models.StartWorkflowExecutionResponse, - schedule_to_close_timeout=None, - schedule_to_start_timeout=None, - start_to_close_timeout=None, - cancellation_type=NexusOperationCancellationType.WAIT_COMPLETED, - headers=None, - summary=None, - ) - ) - - async def start_cancel_run( - self, request: models.CancelRunRequest - ) -> temporalio.workflow.NexusOperationHandle[models.CancelRunResponse]: - from temporalio.worker._interceptor import StartNexusOperationInput - from temporalio.nexus.system import TEMPORAL_SYSTEM_ENDPOINT - from temporalio.workflow import NexusOperationCancellationType - - return await self._intercept_system_nexus_operation( - StartNexusOperationInput( - endpoint=TEMPORAL_SYSTEM_ENDPOINT, - service="temporal.example.v1.WorkflowRunService", - operation="CancelRun", - input=request, - output_type=models.CancelRunResponse, - schedule_to_close_timeout=None, - schedule_to_start_timeout=None, - start_to_close_timeout=None, - cancellation_type=NexusOperationCancellationType.WAIT_COMPLETED, - headers=None, - summary=None, - ) - ) diff --git a/advanced/samples/python/wit/proto_record_with_resource/models.py b/advanced/samples/python/wit/proto_record_with_resource/models.py deleted file mode 100644 index 872af9e6c..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/models.py +++ /dev/null @@ -1,280 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations - -import dataclasses -import typing_extensions -import datetime -import temporalio.api.common.v1.message_pb2 -import temporalio.api.workflowservice.v1.request_response_pb2 -import temporalio.converter - -import temporal_support as _support - - -@dataclasses.dataclass(slots=True) -class StartRunRequest: - workflow: str - workflow_id: str - task_queue: str - workflow_start_delay: datetime.timedelta | None = None - namespace: str = dataclasses.field(default_factory=_support.workflow_namespace) - - -class _StartRunRequestTransferTypeConverter( - temporalio.converter.TransferTypeConverter[ - StartRunRequest, - temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest, - ] -): - transfer_type: ( - type[ - temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest - ] - | None - ) = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest - - @typing_extensions.override - def from_transfer_type( - self, - value: temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest, - type_hint: type[StartRunRequest], - ) -> StartRunRequest: - if not value.HasField("workflow_type"): - raise ValueError("missing required field StartRunRequest.workflow") - workflow = _support.workflow_type_from_proto(value.workflow_type) - if not value.workflow_id: - raise ValueError("missing required field StartRunRequest.workflow_id") - workflow_id = value.workflow_id - if not value.HasField("task_queue"): - raise ValueError("missing required field StartRunRequest.task_queue") - task_queue = _support.task_queue_from_proto(value.task_queue) - return StartRunRequest( - workflow=workflow, - workflow_id=workflow_id, - task_queue=task_queue, - workflow_start_delay=_support.duration_from_proto( - value.workflow_start_delay - ) - if value.HasField("workflow_start_delay") - else None, - namespace=value.namespace, - ) - - @typing_extensions.override - def to_transfer_type( - self, - value: StartRunRequest, - ) -> temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest: - message = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionRequest() - message.workflow_type.CopyFrom(_support.workflow_type_to_proto(value.workflow)) - message.workflow_id = value.workflow_id - message.task_queue.CopyFrom(_support.task_queue_to_proto(value.task_queue)) - if value.workflow_start_delay is not None: - message.workflow_start_delay.CopyFrom( - _support.duration_to_proto(value.workflow_start_delay) - ) - message.namespace = value.namespace - return message - - -temporalio.converter.transfer_type_convertible(_StartRunRequestTransferTypeConverter)( - StartRunRequest -) # pyright: ignore[reportUnusedCallResult] - - -@dataclasses.dataclass(slots=True) -class StartWorkflowExecutionResponse: - run_id: str | None = None - started: bool | None = None - - -class _StartWorkflowExecutionResponseTransferTypeConverter( - temporalio.converter.TransferTypeConverter[ - StartWorkflowExecutionResponse, - temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse, - ] -): - transfer_type: ( - type[ - temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse - ] - | None - ) = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse - - @typing_extensions.override - def from_transfer_type( - self, - value: temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse, - type_hint: type[StartWorkflowExecutionResponse], - ) -> StartWorkflowExecutionResponse: - return StartWorkflowExecutionResponse( - run_id=value.run_id if bool(value.run_id) else None, - started=value.started if bool(value.started) else None, - ) - - @typing_extensions.override - def to_transfer_type( - self, - value: StartWorkflowExecutionResponse, - ) -> temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse: - message = temporalio.api.workflowservice.v1.request_response_pb2.StartWorkflowExecutionResponse() - if value.run_id is not None: - message.run_id = value.run_id - if value.started is not None: - message.started = value.started - return message - - -temporalio.converter.transfer_type_convertible( - _StartWorkflowExecutionResponseTransferTypeConverter -)(StartWorkflowExecutionResponse) # pyright: ignore[reportUnusedCallResult] - - -@dataclasses.dataclass(slots=True, kw_only=True) -class CancelRunRequest: - namespace: str = dataclasses.field(default_factory=_support.workflow_namespace) - workflow_execution: WorkflowExecution - reason: str | None = None - - -class _CancelRunRequestTransferTypeConverter( - temporalio.converter.TransferTypeConverter[ - CancelRunRequest, - temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest, - ] -): - transfer_type: ( - type[ - temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest - ] - | None - ) = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest - - @typing_extensions.override - def from_transfer_type( - self, - value: temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest, - type_hint: type[CancelRunRequest], - ) -> CancelRunRequest: - if not value.HasField("workflow_execution"): - raise ValueError( - "missing required field CancelRunRequest.workflow_execution" - ) - workflow_execution = ( - _WorkflowExecutionTransferTypeConverter().from_transfer_type( - value.workflow_execution, WorkflowExecution - ) - ) - return CancelRunRequest( - namespace=value.namespace, - workflow_execution=workflow_execution, - reason=value.reason if bool(value.reason) else None, - ) - - @typing_extensions.override - def to_transfer_type( - self, - value: CancelRunRequest, - ) -> temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest: - message = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionRequest() - message.namespace = value.namespace - message.workflow_execution.CopyFrom( - _WorkflowExecutionTransferTypeConverter().to_transfer_type( - value.workflow_execution - ) - ) - if value.reason is not None: - message.reason = value.reason - return message - - -temporalio.converter.transfer_type_convertible(_CancelRunRequestTransferTypeConverter)( - CancelRunRequest -) # pyright: ignore[reportUnusedCallResult] - - -@dataclasses.dataclass(slots=True) -class WorkflowExecution: - workflow_id: str - run_id: str | None = None - - -class _WorkflowExecutionTransferTypeConverter( - temporalio.converter.TransferTypeConverter[ - WorkflowExecution, temporalio.api.common.v1.message_pb2.WorkflowExecution - ] -): - transfer_type: ( - type[temporalio.api.common.v1.message_pb2.WorkflowExecution] | None - ) = temporalio.api.common.v1.message_pb2.WorkflowExecution - - @typing_extensions.override - def from_transfer_type( - self, - value: temporalio.api.common.v1.message_pb2.WorkflowExecution, - type_hint: type[WorkflowExecution], - ) -> WorkflowExecution: - if not value.workflow_id: - raise ValueError("missing required field WorkflowExecution.workflow_id") - workflow_id = value.workflow_id - return WorkflowExecution( - workflow_id=workflow_id, - run_id=value.run_id if bool(value.run_id) else None, - ) - - @typing_extensions.override - def to_transfer_type( - self, - value: WorkflowExecution, - ) -> temporalio.api.common.v1.message_pb2.WorkflowExecution: - message = temporalio.api.common.v1.message_pb2.WorkflowExecution() - message.workflow_id = value.workflow_id - if value.run_id is not None: - message.run_id = value.run_id - return message - - -temporalio.converter.transfer_type_convertible(_WorkflowExecutionTransferTypeConverter)( - WorkflowExecution -) # pyright: ignore[reportUnusedCallResult] - - -@dataclasses.dataclass(slots=True) -class CancelRunResponse: - pass - - -class _CancelRunResponseTransferTypeConverter( - temporalio.converter.TransferTypeConverter[ - CancelRunResponse, - temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse, - ] -): - transfer_type: ( - type[ - temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse - ] - | None - ) = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse - - @typing_extensions.override - def from_transfer_type( - self, - value: temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse, - type_hint: type[CancelRunResponse], - ) -> CancelRunResponse: - return CancelRunResponse() - - @typing_extensions.override - def to_transfer_type( - self, - value: CancelRunResponse, - ) -> temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse: - message = temporalio.api.workflowservice.v1.request_response_pb2.RequestCancelWorkflowExecutionResponse() - return message - - -temporalio.converter.transfer_type_convertible(_CancelRunResponseTransferTypeConverter)( - CancelRunResponse -) # pyright: ignore[reportUnusedCallResult] diff --git a/advanced/samples/python/wit/proto_record_with_resource/operations/__init__.py b/advanced/samples/python/wit/proto_record_with_resource/operations/__init__.py deleted file mode 100644 index ca23418da..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/operations/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations diff --git a/advanced/samples/python/wit/proto_record_with_resource/operations/start_run.py b/advanced/samples/python/wit/proto_record_with_resource/operations/start_run.py deleted file mode 100644 index 50be20fcd..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/operations/start_run.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations - -import datetime -import temporalio.workflow - -from ..models import ( - StartRunRequest, - StartWorkflowExecutionResponse, -) -from .._resources import ( - StartRunResult, - WorkflowRun, -) - - -async def _start_run( - request: StartRunRequest, -) -> StartRunResult: - nexus_client = temporalio.workflow.create_nexus_client( - service="temporal.example.v1.WorkflowRunService", - endpoint="__temporal_system", - ) - handle = await nexus_client.start_operation( - operation="StartRun", - input=request, - output_type=StartWorkflowExecutionResponse, - ) - result = await handle - return StartRunResult( - run=WorkflowRun( - workflow_id=request.workflow_id, - run_id=result.run_id or None, - ), - started=result.started, - ) - - -async def start_run( - *, - workflow: str, - workflow_id: str, - task_queue: str, - workflow_start_delay: datetime.timedelta | None = None, -) -> StartRunResult: - request = StartRunRequest( - workflow=workflow, - workflow_id=workflow_id, - task_queue=task_queue, - workflow_start_delay=workflow_start_delay, - ) - return await _start_run(request) diff --git a/advanced/samples/python/wit/proto_record_with_resource/services.py b/advanced/samples/python/wit/proto_record_with_resource/services.py deleted file mode 100644 index 4d6048dc2..000000000 --- a/advanced/samples/python/wit/proto_record_with_resource/services.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by nexgen latest. DO NOT EDIT! - -from __future__ import annotations - -from nexusrpc import Operation, service -from .models import ( - CancelRunRequest, - CancelRunResponse, - StartRunRequest, - StartWorkflowExecutionResponse, -) - - -@service(name="temporal.example.v1.WorkflowRunService") -class WorkflowRunService: - start_run: Operation[ - StartRunRequest, - StartWorkflowExecutionResponse, - ] = Operation(name="StartRun") - - cancel_run: Operation[ - CancelRunRequest, - CancelRunResponse, - ] = Operation(name="CancelRun") diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/index.ts b/advanced/samples/typescript/wit/proto-record-with-resource/index.ts deleted file mode 100644 index f3eac7411..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -export { startRun } from "./operations/start-run"; -export { cancelRun } from "./operations/cancel-run"; -export type { - CancelRunRequest, - CancelRunResponse, - StartRunRequest, - StartWorkflowExecutionResponse, -} from "./models"; diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/interceptors.ts b/advanced/samples/typescript/wit/proto-record-with-resource/interceptors.ts deleted file mode 100644 index c4c361b75..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/interceptors.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -import { composeInterceptors } from "../../../interceptor-composition"; -import type * as nexus from "../../../nexus"; -import type { - StartWorkflowExecutionResponse, - StartRunRequest, - CancelRunResponse, - CancelRunRequest, -} from "./models"; - -export interface SystemNexusWorkflowOutboundCallsInterceptor { - startRun?: ( - input: StartRunRequest, - next: ( - input: StartRunRequest, - ) => Promise>, - ) => Promise>; - cancelRun?: ( - input: CancelRunRequest, - next: ( - input: CancelRunRequest, - ) => Promise>, - ) => Promise>; -} - -/** Dispatches the operation-specific interceptor chain selected by service and operation. */ -export function dispatchSystemNexusSpecificInterceptors( - service: string, - operation: string, - interceptors: SystemNexusWorkflowOutboundCallsInterceptor[], - input: unknown, - next: (input: Input) => Promise>, -): Promise> { - switch (service) { - case "temporal.example.v1.WorkflowRunService": - switch (operation) { - case "StartRun": - return composeInterceptors(interceptors, "startRun", (request) => - next(request), - )(input as StartRunRequest) as Promise>; - case "CancelRun": - return composeInterceptors(interceptors, "cancelRun", (request) => - next(request), - )(input as CancelRunRequest) as Promise>; - default: - return next(input); - } - default: - return next(input); - } -} diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/models.ts b/advanced/samples/typescript/wit/proto-record-with-resource/models.ts deleted file mode 100644 index 7b2ed31ea..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/models.ts +++ /dev/null @@ -1,245 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -import type * as common from "@temporalio/common"; -import type { temporal } from "@temporalio/proto"; -import * as support from "../../support/temporal_model_converters"; -import type { WorkflowRun } from "./resources"; - -function requiredField( - value: T | null | undefined, - owner: string, - field: string, -): T { - if (value == null) { - throw new Error(`missing required field ${owner}.${field}`); - } - return value; -} - -export interface StartWorkflowExecutionResponse { - runId?: string; - started?: boolean; -} - -function startWorkflowExecutionResponseFromProto( - proto: temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse, -): StartWorkflowExecutionResponse { - return { - runId: proto.runId ?? undefined, - started: proto.started ?? undefined, - }; -} - -function startWorkflowExecutionResponseToProto( - model: StartWorkflowExecutionResponse, -): temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse { - return { - runId: model.runId, - started: model.started, - }; -} - -export const startWorkflowExecutionResponseTransferTypeConverter = { - fromTransferType( - value: temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse, - ): StartWorkflowExecutionResponse { - return startWorkflowExecutionResponseFromProto(value); - }, - - toTransferType( - value: StartWorkflowExecutionResponse, - ): temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse { - return startWorkflowExecutionResponseToProto(value); - }, -}; -export interface StartRunRequest { - workflow: string; - workflowId: string; - taskQueue: string; - workflowStartDelay?: common.Duration; - namespace: string; -} - -function startRunRequestFromProto( - proto: temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest, -): StartRunRequest { - return { - workflow: requiredField( - support.workflowTypeFromProto( - requiredField(proto.workflowType, "StartRunRequest", "workflow"), - ), - "StartRunRequest", - "workflow", - ), - workflowId: requiredField( - proto.workflowId === "" ? undefined : proto.workflowId, - "StartRunRequest", - "workflowId", - ), - taskQueue: requiredField( - support.taskQueueFromProto( - requiredField(proto.taskQueue, "StartRunRequest", "taskQueue"), - ), - "StartRunRequest", - "taskQueue", - ), - workflowStartDelay: - proto.workflowStartDelay == null - ? undefined - : support.durationFromProto(proto.workflowStartDelay), - namespace: requiredField( - proto.namespace === "" ? undefined : proto.namespace, - "sourced field", - "namespace", - ), - }; -} - -function startRunRequestToProto( - model: StartRunRequest, -): temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest { - return { - workflowType: support.workflowTypeToProto( - requiredField(model.workflow, "StartRunRequest", "workflow"), - ), - workflowId: requiredField(model.workflowId, "StartRunRequest", "workflowId"), - taskQueue: support.taskQueueToProto( - requiredField(model.taskQueue, "StartRunRequest", "taskQueue"), - ), - workflowStartDelay: - model.workflowStartDelay == null - ? undefined - : support.durationToProto(model.workflowStartDelay), - namespace: model.namespace ?? support.workflowNamespace(), - }; -} - -export const startRunRequestTransferTypeConverter = { - fromTransferType( - value: temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest, - ): StartRunRequest { - return startRunRequestFromProto(value); - }, - - toTransferType( - value: StartRunRequest, - ): temporal.api.workflowservice.v1.IStartWorkflowExecutionRequest { - return startRunRequestToProto(value); - }, -}; -export type CancelRunResponse = Record; - -function cancelRunResponseFromProto( - proto: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse, -): CancelRunResponse { - return {}; -} - -function cancelRunResponseToProto( - model: CancelRunResponse, -): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse { - return {}; -} - -export const cancelRunResponseTransferTypeConverter = { - fromTransferType( - value: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse, - ): CancelRunResponse { - return cancelRunResponseFromProto(value); - }, - - toTransferType( - value: CancelRunResponse, - ): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionResponse { - return cancelRunResponseToProto(value); - }, -}; -export interface CancelRunRequest { - workflowExecution: WorkflowExecution; - reason?: string; - namespace: string; -} - -function cancelRunRequestFromProto( - proto: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest, -): CancelRunRequest { - return { - workflowExecution: workflowExecutionFromProto( - requiredField(proto.workflowExecution, "CancelRunRequest", "workflowExecution"), - ), - reason: proto.reason ?? undefined, - namespace: requiredField( - proto.namespace === "" ? undefined : proto.namespace, - "sourced field", - "namespace", - ), - }; -} - -function cancelRunRequestToProto( - model: CancelRunRequest, -): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest { - return { - workflowExecution: workflowExecutionToProto( - requiredField(model.workflowExecution, "CancelRunRequest", "workflowExecution"), - ), - reason: model.reason, - namespace: model.namespace ?? support.workflowNamespace(), - }; -} - -export const cancelRunRequestTransferTypeConverter = { - fromTransferType( - value: temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest, - ): CancelRunRequest { - return cancelRunRequestFromProto(value); - }, - - toTransferType( - value: CancelRunRequest, - ): temporal.api.workflowservice.v1.IRequestCancelWorkflowExecutionRequest { - return cancelRunRequestToProto(value); - }, -}; -export interface WorkflowExecution { - workflowId: string; - runId?: string; -} - -function workflowExecutionFromProto( - proto: temporal.api.common.v1.IWorkflowExecution, -): WorkflowExecution { - return { - workflowId: requiredField( - proto.workflowId === "" ? undefined : proto.workflowId, - "WorkflowExecution", - "workflowId", - ), - runId: proto.runId ?? undefined, - }; -} - -function workflowExecutionToProto( - model: WorkflowExecution, -): temporal.api.common.v1.IWorkflowExecution { - return { - workflowId: requiredField(model.workflowId, "WorkflowExecution", "workflowId"), - runId: model.runId, - }; -} - -export const workflowExecutionTransferTypeConverter = { - fromTransferType( - value: temporal.api.common.v1.IWorkflowExecution, - ): WorkflowExecution { - return workflowExecutionFromProto(value); - }, - - toTransferType(value: WorkflowExecution): temporal.api.common.v1.IWorkflowExecution { - return workflowExecutionToProto(value); - }, -}; -export interface StartRunResult { - run: WorkflowRun; - started?: boolean; -} diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/operations/cancel-run.ts b/advanced/samples/typescript/wit/proto-record-with-resource/operations/cancel-run.ts deleted file mode 100644 index de014d7bb..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/operations/cancel-run.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -import * as nexus from "../../../../nexus"; -import { workflowRunService } from "../services"; -import type { CancelRunResponse, CancelRunRequest } from "../models"; -import * as support from "../../../support/temporal_model_converters"; - -type CancelRunInput = CancelRunRequest extends infer Input - ? Input extends unknown - ? Omit & { namespace?: never } - : never - : never; - -/** - * @param request - Request for the operation. - */ -export async function cancelRun( - requestInput: CancelRunInput, -): Promise> { - const request = { - ...requestInput, - namespace: support.workflowNamespace(), - } as CancelRunRequest; - const client = nexus.createNexusServiceClient({ - service: workflowRunService, - endpoint: "temporal-system", - }); - return await client.startOperation(workflowRunService.operations.cancelRun, request); -} diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/operations/start-run.ts b/advanced/samples/typescript/wit/proto-record-with-resource/operations/start-run.ts deleted file mode 100644 index 661c319b7..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/operations/start-run.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -import * as nexus from "../../../../nexus"; -import { workflowRunService } from "../services"; -import type { StartRunRequest, StartRunResult } from "../models"; -import * as support from "../../../support/temporal_model_converters"; -import { WorkflowRun } from "../resources"; - -type StartRunInput = StartRunRequest extends infer Input - ? Input extends unknown - ? Omit & { namespace?: never } - : never - : never; - -/** - * @param request - Request for the operation. - */ -export async function startRun(requestInput: StartRunInput): Promise { - const request = { - ...requestInput, - namespace: support.workflowNamespace(), - } as StartRunRequest; - const client = nexus.createNexusServiceClient({ - service: workflowRunService, - endpoint: "temporal-system", - }); - const handle = await client.startOperation( - workflowRunService.operations.startRun, - request, - ); - const result = await handle.result(); - return { - run: new WorkflowRun(request.workflowId, result.runId ?? undefined), - started: result.started, - }; -} diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/registry.ts b/advanced/samples/typescript/wit/proto-record-with-resource/registry.ts deleted file mode 100644 index 135000ef6..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/registry.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -export const operationRegistry = [ - { - service: "temporal.example.v1.WorkflowRunService", - operation: "StartRun", - }, - { - service: "temporal.example.v1.WorkflowRunService", - operation: "CancelRun", - }, -] as const; diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/resources.ts b/advanced/samples/typescript/wit/proto-record-with-resource/resources.ts deleted file mode 100644 index 9ccdd3496..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/resources.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -import { cancelRun } from "./operations/cancel-run"; - -export class WorkflowRun { - public constructor( - public readonly workflowId: string, - public readonly runId: string | undefined, - ) {} - - public async cancel(reason?: string): Promise { - const request = { - workflowExecution: { workflowId: this.workflowId, runId: this.runId }, - reason: reason, - }; - const handle = await cancelRun(request); - await handle.result(); - } -} diff --git a/advanced/samples/typescript/wit/proto-record-with-resource/services.ts b/advanced/samples/typescript/wit/proto-record-with-resource/services.ts deleted file mode 100644 index aa9bd4494..000000000 --- a/advanced/samples/typescript/wit/proto-record-with-resource/services.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Generated by nexgen latest. DO NOT EDIT! - -import * as nexus from "nexus-rpc"; -import { - startWorkflowExecutionResponseTransferTypeConverter, - startRunRequestTransferTypeConverter, - cancelRunResponseTransferTypeConverter, - cancelRunRequestTransferTypeConverter, -} from "./models"; -import type { - StartWorkflowExecutionResponse, - StartRunRequest, - CancelRunResponse, - CancelRunRequest, -} from "./models"; - -export const workflowRunService = nexus.service( - "temporal.example.v1.WorkflowRunService", - { - startRun: nexus.operation({ - name: "StartRun", - inputType: { transferTypeConverter: startRunRequestTransferTypeConverter }, - outputType: { - transferTypeConverter: startWorkflowExecutionResponseTransferTypeConverter, - }, - }), - cancelRun: nexus.operation({ - name: "CancelRun", - inputType: { transferTypeConverter: cancelRunRequestTransferTypeConverter }, - outputType: { transferTypeConverter: cancelRunResponseTransferTypeConverter }, - }), - }, -); diff --git a/xtask/src/validate.rs b/xtask/src/validate.rs index cbb36f432..b21571bf2 100644 --- a/xtask/src/validate.rs +++ b/xtask/src/validate.rs @@ -357,16 +357,6 @@ fn validate_dotnet(repo_root: &Path, log: &mut ValidationLog) -> Result<()> { "--nologo", ], )?; - run( - log, - &workflow_service_docs_root, - "dotnet", - &[ - "build", - "Nexgen.DotNetProtoRecordWithResource.csproj", - "--nologo", - ], - )?; Ok(()) }