From 974f5fbbdcbe3a931293772474ec399dde3bdd5d Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Sat, 11 Apr 2026 15:13:00 -0700 Subject: [PATCH 1/3] Add sample for stand alone nexus operations --- go.mod | 23 ++-- go.sum | 42 +++--- nexus-standalone-operations/README.md | 70 ++++++++++ nexus-standalone-operations/handler_test.go | 145 ++++++++++++++++++++ nexus-standalone-operations/starter/main.go | 100 ++++++++++++++ nexus-standalone-operations/worker/main.go | 43 ++++++ 6 files changed, 392 insertions(+), 31 deletions(-) create mode 100644 nexus-standalone-operations/README.md create mode 100644 nexus-standalone-operations/handler_test.go create mode 100644 nexus-standalone-operations/starter/main.go create mode 100644 nexus-standalone-operations/worker/main.go diff --git a/go.mod b/go.mod index 61d767a4..c61af2b7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/temporalio/samples-go -go 1.25.0 +go 1.25.4 replace github.com/cactus/go-statsd-client => github.com/cactus/go-statsd-client/v5 v5.0.0 @@ -25,8 +25,8 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 go.opentelemetry.io/otel/sdk v1.42.0 go.opentelemetry.io/otel/trace v1.42.0 - go.temporal.io/api v1.62.12 - go.temporal.io/sdk v1.45.0 + go.temporal.io/api v1.63.0 + go.temporal.io/sdk v1.46.0 go.temporal.io/sdk/contrib/aws/lambdaworker v0.1.1 go.temporal.io/sdk/contrib/aws/lambdaworker/otel v0.1.1 go.temporal.io/sdk/contrib/aws/s3driver v0.2.0 @@ -111,6 +111,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nexus-rpc/nexus-proto-annotations v0.1.0 // indirect github.com/outcaste-io/ristretto v0.2.3 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -156,16 +157,16 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect - golang.org/x/crypto v0.48.0 // indirect + golang.org/x/crypto v0.51.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.41.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + golang.org/x/tools v0.44.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 072fede1..680e3643 100644 --- a/go.sum +++ b/go.sum @@ -272,6 +272,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0 h1:2fELd+9sqUtNu6Fg//pw8YFsxOvp8vZ8hfP0nHhNI80= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -477,11 +479,11 @@ go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.0.1/go.mod h1:C7EH go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d h1:Ns9kd1Rwzw7t0BR8XMphenji4SmIoNZPn8zhYmaVKP8= go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d/go.mod h1:92Uoe3l++MlthCm+koNi0tcUCX3anayogF0Pa/sp24k= go.temporal.io/api v1.5.0/go.mod h1:BqKxEJJYdxb5dqf0ODfzfMxh8UEQ5L3zKS51FiIYYkA= -go.temporal.io/api v1.62.12 h1:627rVnItegQmrszg1bH4vfyc/1uNo5qCereCNkvZefw= -go.temporal.io/api v1.62.12/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.63.0 h1:YZFOTA0/thRUIUC4qunAWdHhPh/IG4vy/+WjfEvT+ZE= +go.temporal.io/api v1.63.0/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= go.temporal.io/sdk v1.12.0/go.mod h1:lSp3lH1lI0TyOsus0arnO3FYvjVXBZGi/G7DjnAnm6o= -go.temporal.io/sdk v1.45.0 h1:kvsczo3SHTS60+zBWH9lljLmBLWSXcyGkBxr88z8iQI= -go.temporal.io/sdk v1.45.0/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= +go.temporal.io/sdk v1.46.0 h1:zD2l907+4iVkLsnJZwFj/oIIjYsoqyjsHlKO/3tDKoU= +go.temporal.io/sdk v1.46.0/go.mod h1:x3v/9ImVh469kiHspoq1xgLdPnetbfuCAm+Y1+sUtIo= go.temporal.io/sdk/contrib/aws/lambdaworker v0.1.1 h1:AQBa7CN+EOWhZaf4vr46TfxTZM7yCsvrzAsKqrExxmQ= go.temporal.io/sdk/contrib/aws/lambdaworker v0.1.1/go.mod h1:Rgn/tlb4MDNAAjnXKNgHui4IY+MogMCk4Y4c2YA6Dcc= go.temporal.io/sdk/contrib/aws/lambdaworker/otel v0.1.1 h1:sMTtpD5jsb4FeJadYkoOOzb84oWYK2/g0keb3IRO6xY= @@ -530,8 +532,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -554,8 +556,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -572,8 +574,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -586,8 +588,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -615,8 +617,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -624,8 +626,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= @@ -645,14 +647,14 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= diff --git a/nexus-standalone-operations/README.md b/nexus-standalone-operations/README.md new file mode 100644 index 00000000..b3fe677d --- /dev/null +++ b/nexus-standalone-operations/README.md @@ -0,0 +1,70 @@ +This sample demonstrates how to use Standalone Nexus Operations (executing Nexus operations directly from client code without wrapping them in a Workflow). +It shows both sync and async (workflow-backed) operations, and how to use the `ListNexusOperations` and `CountNexusOperations` APIs. + +## Note: Standalone Nexus operations require a server version that supports this feature. Use the dev server build at https://github.com/temporalio/cli/releases/tag/v1.7.2-standalone-nexus-operations. + +### Steps to run this sample (with expected output): +1) Run the [Temporal dev server build that supports standalone Nexus operations](https://github.com/temporalio/cli/releases/tag/v1.7.2-standalone-nexus-operations). (If you are going to run locally, you will want to start it in another terminal; this command is blocking and runs until it receives a SIGINT (Ctrl + C) command.) + +Start the dev server with the dynamic config flags required for standalone Nexus operations: + +```bash +temporal server start-dev \ + --dynamic-config-value "nexusoperation.enableStandalone=true" \ + --dynamic-config-value "history.enableChasmCallbacks=true" +``` + +You should see a line about the CLI, Server and UI versions, and one line each for the Server URL, UI URL and Metrics endpoint. It should look something like this: + +```bash +Temporal CLI 1.7.2-standalone-nexus-operations (Server 1.32.0-155.0, UI 2.49.1) + +Temporal Server: localhost:7233 +Temporal UI: http://localhost:8233 +Temporal Metrics: http://localhost:61951/metrics +``` + +2) Create a Nexus endpoint that routes to the worker's task queue. In a second terminal, run: +```bash +temporal operator nexus endpoint create \ + --name nexus-standalone-operations-endpoint \ + --target-namespace default \ + --target-task-queue nexus-standalone-operations +``` + +1) Then run the following command to start the worker. The worker is a blocking process that runs until it receives a SIGINT (Ctrl + C) command. +```bash +go run nexus-standalone-operations/worker/main.go +``` + +You should see the following log line: +1. Starting the Worker with Namespace `default`, and TaskQueue `nexus-standalone-operations` and it will list the WorkerID for the created worker. + +For example: +```bash +2026/05/21 08:59:49 INFO Started Worker Namespace default TaskQueue nexus-standalone-operations WorkerID 71172 +``` + +> [!NOTE] +> Timestamps and IDs will differ on your machine. + +4) In a third terminal, run the following command to start the example: +```bash +go run nexus-standalone-operations/starter/main.go +``` + +You should see something similar to the following output: + +```bash +2026/05/21 09:00:30 Started Echo operation OperationID nexus-standalone-echo-op +2026/05/21 09:00:30 Echo result: hello +2026/05/21 09:00:30 Started Hello operation OperationID nexus-standalone-hello-op +2026/05/21 09:00:30 Hello result: Hello Temporal 👋 +2026/05/21 09:00:30 ListNexusOperations results: +2026/05/21 09:00:30 OperationID: nexus-standalone-hello-op, Operation: say-hello, Status: Completed +2026/05/21 09:00:30 OperationID: nexus-standalone-echo-op, Operation: echo, Status: Completed +2026/05/21 09:00:30 Total Nexus operations: 2 +``` + +If you run the starter code multiple times, you should see additional `ListNexusOperations` results, as more operations are run. +The same goes for the number from `CountNexusOperations`. diff --git a/nexus-standalone-operations/handler_test.go b/nexus-standalone-operations/handler_test.go new file mode 100644 index 00000000..8e7d3a23 --- /dev/null +++ b/nexus-standalone-operations/handler_test.go @@ -0,0 +1,145 @@ +package nexus_standalone_operations_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/nexus-rpc/sdk-go/nexus" + + nexuspb "go.temporal.io/api/nexus/v1" + operatorservice "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/testsuite" + "go.temporal.io/sdk/worker" + + "github.com/temporalio/samples-go/nexus/handler" + "github.com/temporalio/samples-go/nexus/service" +) + +const ( + taskQueue = "nexus-standalone-operations-test" + endpointName = "nexus-standalone-operations-test-endpoint" +) + +func Test_StandaloneNexusOperations_Using_DevServer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + // Start the dev server with standalone Nexus support. + server, err := testsuite.StartDevServer(ctx, testsuite.DevServerOptions{ + CachedDownload: testsuite.CachedDownload{ + Version: "v1.7.2-standalone-nexus-operations", + }, + ExtraArgs: []string{ + "--dynamic-config-value", "nexusoperation.enableStandalone=true", + "--dynamic-config-value", "history.enableChasmCallbacks=true", + }, + }) + require.NoError(t, err) + defer func() { _ = server.Stop() }() + + c := server.Client() + + // Create a Nexus endpoint targeting our task queue. + _, err = c.OperatorService().CreateNexusEndpoint(ctx, &operatorservice.CreateNexusEndpointRequest{ + Spec: &nexuspb.EndpointSpec{ + Name: endpointName, + Target: &nexuspb.EndpointTarget{ + Variant: &nexuspb.EndpointTarget_Worker_{ + Worker: &nexuspb.EndpointTarget_Worker{ + Namespace: "default", + TaskQueue: taskQueue, + }, + }, + }, + }, + }) + require.NoError(t, err) + + // Register Nexus operations on the worker, reusing the handler from the nexus sample. + w := worker.New(c, taskQueue, worker.Options{}) + + svc := nexus.NewService(service.HelloServiceName) + require.NoError(t, svc.Register(handler.EchoOperation, handler.HelloOperation)) + w.RegisterNexusService(svc) + w.RegisterWorkflow(handler.HelloHandlerWorkflow) + require.NoError(t, w.Start()) + defer w.Stop() + + // Create a standalone NexusClient. + nexusClient, err := c.NewNexusClient(client.NexusClientOptions{ + Endpoint: endpointName, + Service: service.HelloServiceName, + }) + require.NoError(t, err) + + // Test sync operation (Echo). + t.Run("Echo sync operation", func(t *testing.T) { + input := service.EchoInput{Message: "hello-nexus"} + handle, err := nexusClient.ExecuteOperation(ctx, service.EchoOperationName, input, client.StartNexusOperationOptions{ + ID: uuid.NewString(), + ScheduleToCloseTimeout: 10 * time.Second, + }) + require.NoError(t, err) + require.NotEmpty(t, handle.GetID()) + + var result service.EchoOutput + err = handle.Get(ctx, &result) + require.NoError(t, err) + require.Equal(t, "hello-nexus", result.Message) + }) + + // Test async operation (Hello). + t.Run("Hello async operation", func(t *testing.T) { + input := service.HelloInput{Name: "Temporal", Language: service.EN} + handle, err := nexusClient.ExecuteOperation(ctx, service.HelloOperationName, input, client.StartNexusOperationOptions{ + ID: uuid.NewString(), + ScheduleToCloseTimeout: 10 * time.Second, + }) + require.NoError(t, err) + require.NotEmpty(t, handle.GetID()) + + var result service.HelloOutput + err = handle.Get(ctx, &result) + require.NoError(t, err) + require.Equal(t, "Hello Temporal 👋", result.Message) + }) + + // Test ListNexusOperations (on client.Client, not NexusClient). + t.Run("List operations", func(t *testing.T) { + require.Eventually(t, func() bool { + resp, listErr := c.ListNexusOperations(ctx, client.ListNexusOperationsOptions{ + Query: fmt.Sprintf("Endpoint = '%s'", endpointName), + }) + if listErr != nil { + return false + } + count := 0 + for metadata, iterErr := range resp.Results { + if iterErr != nil { + return false + } + if metadata.OperationID == "" || metadata.Endpoint != endpointName { + return false + } + count++ + } + return count > 0 + }, 10*time.Second, 500*time.Millisecond, "timed out waiting for operations to appear in list") + }) + + // Test CountNexusOperations (on client.Client, not NexusClient). + t.Run("Count operations", func(t *testing.T) { + require.Eventually(t, func() bool { + resp, countErr := c.CountNexusOperations(ctx, client.CountNexusOperationsOptions{ + Query: fmt.Sprintf("Endpoint = '%s'", endpointName), + }) + return countErr == nil && resp.Count > 0 + }, 10*time.Second, 500*time.Millisecond, "timed out waiting for count to reflect operations") + }) +} diff --git a/nexus-standalone-operations/starter/main.go b/nexus-standalone-operations/starter/main.go new file mode 100644 index 00000000..dda3e51b --- /dev/null +++ b/nexus-standalone-operations/starter/main.go @@ -0,0 +1,100 @@ +// @@@SNIPSTART samples-go-nexus-standalone-operations-starter +package main + +import ( + "context" + "fmt" + "log" + "time" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/contrib/envconfig" + + "github.com/temporalio/samples-go/nexus/service" +) + +// This sample demonstrates standalone Nexus operations — executing Nexus operations +// directly from client code without wrapping them in a workflow. + +const endpointName = "nexus-standalone-operations-endpoint" + +func main() { + // The client is a heavyweight object that should be created once per process. + c, err := client.Dial(envconfig.MustLoadDefaultClientOptions()) + if err != nil { + log.Fatalln("Unable to create client", err) + } + defer c.Close() + + // Create a NexusClient bound to the endpoint and service. + // The endpoint must be pre-created on the server (see README). + nexusClient, err := c.NewNexusClient(client.NexusClientOptions{ + Endpoint: endpointName, + Service: service.HelloServiceName, + }) + if err != nil { + log.Fatalln("Unable to create Nexus client", err) + } + + // Execute the sync Echo operation. + echoHandle, err := nexusClient.ExecuteOperation(context.Background(), service.EchoOperationName, service.EchoInput{Message: "hello"}, client.StartNexusOperationOptions{ + ID: "nexus-standalone-echo-op", + ScheduleToCloseTimeout: 10 * time.Second, + }) + if err != nil { + log.Fatalln("Unable to execute Echo operation", err) + } + log.Println("Started Echo operation", "OperationID", echoHandle.GetID()) + + var echoResult service.EchoOutput + err = echoHandle.Get(context.Background(), &echoResult) + if err != nil { + log.Fatalln("Unable to get Echo operation result", err) + } + log.Println("Echo result:", echoResult.Message) + + // Execute the async (workflow-backed) Hello operation. + helloHandle, err := nexusClient.ExecuteOperation(context.Background(), service.HelloOperationName, service.HelloInput{Name: "Temporal", Language: service.EN}, client.StartNexusOperationOptions{ + ID: "nexus-standalone-hello-op", + ScheduleToCloseTimeout: 10 * time.Second, + }) + if err != nil { + log.Fatalln("Unable to execute Hello operation", err) + } + log.Println("Started Hello operation", "OperationID", helloHandle.GetID()) + + var helloResult service.HelloOutput + err = helloHandle.Get(context.Background(), &helloResult) + if err != nil { + log.Fatalln("Unable to get Hello operation result", err) + } + log.Println("Hello result:", helloResult.Message) + + // List Nexus operations using client.Client (not client.NexusClient). + listResp, err := c.ListNexusOperations(context.Background(), client.ListNexusOperationsOptions{ + Query: fmt.Sprintf("Endpoint = '%s'", endpointName), + }) + if err != nil { + log.Fatalln("Unable to list Nexus operations", err) + } + + log.Println("ListNexusOperations results:") + for metadata, err := range listResp.Results { + if err != nil { + log.Fatalln("Error iterating operations", err) + } + log.Printf("\tOperationID: %s, Operation: %s, Status: %v\n", + metadata.OperationID, metadata.Operation, metadata.Status) + } + + // Count Nexus operations using the base client (not NexusClient). + countResp, err := c.CountNexusOperations(context.Background(), client.CountNexusOperationsOptions{ + Query: fmt.Sprintf("Endpoint = '%s'", endpointName), + }) + if err != nil { + log.Fatalln("Unable to count Nexus operations", err) + } + log.Println("Total Nexus operations:", countResp.Count) +} + +// @@@SNIPEND diff --git a/nexus-standalone-operations/worker/main.go b/nexus-standalone-operations/worker/main.go new file mode 100644 index 00000000..687227d9 --- /dev/null +++ b/nexus-standalone-operations/worker/main.go @@ -0,0 +1,43 @@ +// @@@SNIPSTART samples-go-nexus-standalone-operations-worker +package main + +import ( + "log" + + "github.com/nexus-rpc/sdk-go/nexus" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/contrib/envconfig" + "go.temporal.io/sdk/worker" + + "github.com/temporalio/samples-go/nexus/handler" + "github.com/temporalio/samples-go/nexus/service" +) + +const taskQueue = "nexus-standalone-operations" + +func main() { + // The client and worker are heavyweight objects that should be created once per process. + c, err := client.Dial(envconfig.MustLoadDefaultClientOptions()) + if err != nil { + log.Fatalln("Unable to create client", err) + } + defer c.Close() + + w := worker.New(c, taskQueue, worker.Options{}) + + svc := nexus.NewService(service.HelloServiceName) + err = svc.Register(handler.EchoOperation, handler.HelloOperation) + if err != nil { + log.Fatalln("Unable to register operations", err) + } + w.RegisterNexusService(svc) + w.RegisterWorkflow(handler.HelloHandlerWorkflow) + + err = w.Run(worker.InterruptCh()) + if err != nil { + log.Fatalln("Unable to start worker", err) + } +} + +// @@@SNIPEND From 2fb6401f7176538f2a853ea3c2346856390bd756 Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Thu, 9 Jul 2026 09:15:31 -0700 Subject: [PATCH 2/3] Update CLI version --- nexus-standalone-operations/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nexus-standalone-operations/README.md b/nexus-standalone-operations/README.md index b3fe677d..0a09c1f5 100644 --- a/nexus-standalone-operations/README.md +++ b/nexus-standalone-operations/README.md @@ -1,10 +1,10 @@ This sample demonstrates how to use Standalone Nexus Operations (executing Nexus operations directly from client code without wrapping them in a Workflow). It shows both sync and async (workflow-backed) operations, and how to use the `ListNexusOperations` and `CountNexusOperations` APIs. -## Note: Standalone Nexus operations require a server version that supports this feature. Use the dev server build at https://github.com/temporalio/cli/releases/tag/v1.7.2-standalone-nexus-operations. +## Note: Standalone Nexus operations require a server version that supports this feature. Use the dev server build at https://github.com/temporalio/cli/releases/tag/v1.7.3-standalone-nexus-operations. ### Steps to run this sample (with expected output): -1) Run the [Temporal dev server build that supports standalone Nexus operations](https://github.com/temporalio/cli/releases/tag/v1.7.2-standalone-nexus-operations). (If you are going to run locally, you will want to start it in another terminal; this command is blocking and runs until it receives a SIGINT (Ctrl + C) command.) +1) Run the [Temporal dev server build that supports standalone Nexus operations](https://github.com/temporalio/cli/releases/tag/v1.7.3-standalone-nexus-operations). (If you are going to run locally, you will want to start it in another terminal; this command is blocking and runs until it receives a SIGINT (Ctrl + C) command.) Start the dev server with the dynamic config flags required for standalone Nexus operations: @@ -17,7 +17,7 @@ temporal server start-dev \ You should see a line about the CLI, Server and UI versions, and one line each for the Server URL, UI URL and Metrics endpoint. It should look something like this: ```bash -Temporal CLI 1.7.2-standalone-nexus-operations (Server 1.32.0-155.0, UI 2.49.1) +Temporal CLI 1.7.3-standalone-nexus-operations (Server 1.32.0-158.0, UI 2.52.0) Temporal Server: localhost:7233 Temporal UI: http://localhost:8233 From d0c694040420d063f6ad815eac4edb04829bbc99 Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Thu, 9 Jul 2026 09:22:52 -0700 Subject: [PATCH 3/3] Bump handler test --- nexus-standalone-operations/handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nexus-standalone-operations/handler_test.go b/nexus-standalone-operations/handler_test.go index 8e7d3a23..85f733a3 100644 --- a/nexus-standalone-operations/handler_test.go +++ b/nexus-standalone-operations/handler_test.go @@ -33,7 +33,7 @@ func Test_StandaloneNexusOperations_Using_DevServer(t *testing.T) { // Start the dev server with standalone Nexus support. server, err := testsuite.StartDevServer(ctx, testsuite.DevServerOptions{ CachedDownload: testsuite.CachedDownload{ - Version: "v1.7.2-standalone-nexus-operations", + Version: "v1.7.3-standalone-nexus-operations", }, ExtraArgs: []string{ "--dynamic-config-value", "nexusoperation.enableStandalone=true",