Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions dockerfiles/dynamicconfig/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ frontend.enableUpdateWorkflowExecutionAsyncAccepted:
frontend.workerVersioningWorkflowAPIs:
- value: true
constraints: {}
nexusoperation.enableStandalone:
- value: true
constraints: {}
history.enableCHASMCallbacks:
- value: true
constraints: {}
2 changes: 2 additions & 0 deletions features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
deployment_versioning_routing_with_ramp "github.com/temporalio/features/features/deployment_versioning/routing_with_ramp"
eager_activity_non_remote_activities_worker "github.com/temporalio/features/features/eager_activity/non_remote_activities_worker"
eager_workflow_successful_start "github.com/temporalio/features/features/eager_workflow/successful_start"
nexus_standalone_workflow_run_success "github.com/temporalio/features/features/nexus/standalone_workflow_run_success"
nexus_sync_success "github.com/temporalio/features/features/nexus/sync_success"
query_successful_query "github.com/temporalio/features/features/query/successful_query"
query_timeout_due_to_no_active_workers "github.com/temporalio/features/features/query/timeout_due_to_no_active_workers"
Expand Down Expand Up @@ -94,6 +95,7 @@ func init() {
deployment_versioning_routing_with_ramp.Feature,
eager_activity_non_remote_activities_worker.Feature,
eager_workflow_successful_start.Feature,
nexus_standalone_workflow_run_success.Feature,
nexus_sync_success.Feature,
query_successful_query.Feature,
query_timeout_due_to_no_active_workers.Feature,
Expand Down
19 changes: 19 additions & 0 deletions features/nexus/standalone_workflow_run_success/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Standalone Nexus operation succeeds

A client starts and awaits an async, workflow-backed Nexus operation directly, without a caller
workflow.

# Detailed spec

- A Nexus service with an async, workflow-run operation is registered on the worker.
- The test client builds a standalone `NexusClient` from `client.Client.NewNexusClient` and
calls `ExecuteOperation` against a Nexus endpoint.
- The operation starts asynchronously, backed by a handler workflow; the handler workflow's result
is returned via the operation handle.
- The handle's `Get` blocks until the handler workflow completes and returns the operation result
without any caller workflow being involved.

# Supported SDKs

- Go: requires SDK v1.44.0 or later (the standalone Nexus operation client API is Go-only at this
time).
65 changes: 65 additions & 0 deletions features/nexus/standalone_workflow_run_success/feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package standalone_workflow_run_success

import (
"context"
"fmt"
"time"

"github.com/google/uuid"
"github.com/nexus-rpc/sdk-go/nexus"
"github.com/temporalio/features/harness/go/harness"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporalnexus"
"go.temporal.io/sdk/workflow"
)

const ServiceName = "test-service"

func HandlerWorkflow(ctx workflow.Context, name string) (string, error) {
return "Hello, " + name + "!", nil
}

var AsyncWorkflowOperation = temporalnexus.NewWorkflowRunOperation(
"AsyncWorkflowOperation",
HandlerWorkflow,
func(ctx context.Context, input string, opts nexus.StartOperationOptions) (client.StartWorkflowOptions, error) {
return client.StartWorkflowOptions{ID: "nexus-standalone-handler-" + opts.RequestID}, nil
},
)

var Service = func() *nexus.Service {
s := nexus.NewService(ServiceName)
s.MustRegister(AsyncWorkflowOperation)
return s
}()

var Feature = harness.Feature{
Workflows: HandlerWorkflow,
NexusServices: Service,
Execute: func(ctx context.Context, runner *harness.Runner) (client.WorkflowRun, error) {
// Start a Nexus operation directly from the client, without a caller workflow.
nc, err := runner.Client.NewNexusClient(client.NexusClientOptions{
Endpoint: runner.NexusEndpoint,
Service: ServiceName,
})
if err != nil {
return nil, fmt.Errorf("create nexus client: %w", err)
}
handle, err := nc.ExecuteOperation(ctx, AsyncWorkflowOperation, "world", client.StartNexusOperationOptions{
ID: "standalone-op-" + uuid.NewString(),
ScheduleToCloseTimeout: time.Minute,
})
if err != nil {
return nil, fmt.Errorf("execute operation: %w", err)
}
var result string
if err := handle.Get(ctx, &result); err != nil {
return nil, fmt.Errorf("get operation result: %w", err)
}
if result != "Hello, world!" {
return nil, fmt.Errorf("expected %q, got %q", "Hello, world!", result)
}
// Return nil run so the harness skips the default workflow-run checks.
return nil, nil
},
}
Loading