diff --git a/Makefile b/Makefile index ea42652e9..4f06eaf12 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ ci-build: install proto http-api-docs install: grpc-install api-linter-install buf-install # Run all linters and compile proto files. -proto: grpc http-api-docs nexus-rpc-yaml +proto: grpc http-api-docs system-nexus-wit ######################################################################## ##### Variables ###### @@ -17,7 +17,9 @@ GOPATH := $(shell go env GOPATH) endif GOBIN := $(if $(shell go env GOBIN),$(shell go env GOBIN),$(GOPATH)/bin) -PATH := $(GOBIN):$(PATH) +CARGO_HOME ?= $(HOME)/.cargo +CARGO_BIN := $(CARGO_HOME)/bin +PATH := $(GOBIN):$(CARGO_BIN):$(PATH) STAMPDIR := .stamp COLOR := "\e[1;36m%s\e[0m\n" @@ -33,6 +35,10 @@ PROTO_PATHS = paths=source_relative:$(PROTO_OUT) OAPI_OUT := openapi OAPI3_PATH := .components.schemas.Payload +SYSTEM_NEXUS_WIT := nexus/temporal-system.wit +SYSTEM_NEXUS_SERVICE_PROTO_FILES := $(shell find temporal/api -name "service.proto" | sort) +NEXUS_API_GEN ?= nexus-api-gen + $(PROTO_OUT): mkdir $(PROTO_OUT) @@ -121,21 +127,22 @@ buf-breaking: @printf $(COLOR) "Run buf breaking changes check against master branch..." @(cd $(PROTO_ROOT) && buf breaking --against 'https://github.com/temporalio/api.git#branch=master') -nexus-rpc-yaml: nexus-rpc-yaml-install - printf $(COLOR) "Generate nexus/temporal-proto-models-nexusrpc.yaml..." - mkdir -p nexus +##### Compile system Nexus WIT files ##### +system-nexus-wit: system-nexus-wit-install nexus-api-gen-install + printf $(COLOR) "Generate system Nexus WIT..." protoc -I $(PROTO_ROOT) \ - --nexus-rpc-yaml_opt=nexus-rpc_langs_out=nexus/temporal-proto-models-nexusrpc.yaml \ - --nexus-rpc-yaml_opt=python_package_prefix=temporalio.api \ - --nexus-rpc-yaml_opt=typescript_package_prefix=@temporalio/api \ - --nexus-rpc-yaml_opt=include_operation_tags=exposed \ - --nexus-rpc-yaml_out=. \ - temporal/api/workflowservice/v1/* \ - temporal/api/operatorservice/v1/* - -nexus-rpc-yaml-install: - printf $(COLOR) "Build and install protoc-gen-nexus-rpc-yaml..." - @cd cmd/protoc-gen-nexus-rpc-yaml && go install . + --system-nexus-wit_opt=output=$(SYSTEM_NEXUS_WIT) \ + --system-nexus-wit_opt=nexus_api_gen=$(NEXUS_API_GEN) \ + --system-nexus-wit_out=. \ + $(SYSTEM_NEXUS_SERVICE_PROTO_FILES) + +system-nexus-wit-install: + printf $(COLOR) "Build and install protoc-gen-system-nexus-wit..." + @cd cmd/protoc-gen-system-nexus-wit && go install . + +nexus-api-gen-install: + printf $(COLOR) "Install nexus-api-gen if missing..." + command -v $(NEXUS_API_GEN) >/dev/null || CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install --git https://github.com/temporalio/nexus-api-gen ##### Clean ##### clean: diff --git a/README.md b/README.md index 788613ae8..b31d2f70d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Install as git submodule to the project. ## Contribution Make your change to the temporal/proto files, and run `make` to update the openapi definitions. +Rust is also required because `make` installs and runs `nexus-api-gen` when regenerating system Nexus WIT files. ## Breaking changes diff --git a/cmd/protoc-gen-nexus-rpc-yaml/generator.go b/cmd/protoc-gen-nexus-rpc-yaml/generator.go deleted file mode 100644 index 2e4a939c8..000000000 --- a/cmd/protoc-gen-nexus-rpc-yaml/generator.go +++ /dev/null @@ -1,272 +0,0 @@ -package main - -import ( - "fmt" - "slices" - "sort" - "strings" - - nexusannotationsv1 "github.com/nexus-rpc/nexus-proto-annotations/go/nexusannotations/v1" - "google.golang.org/protobuf/compiler/protogen" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/types/descriptorpb" - "gopkg.in/yaml.v3" -) - -// params holds the parsed protoc plugin options. -// Passed via --nexus-rpc-yaml_opt=key=value (multiple opts are comma-joined by protoc). -// -// - nexus-rpc_langs_out: optional. Output path for the langs YAML. -// If empty, nothing is written. -// Example: "nexus/temporal-proto-models-nexusrpc.yaml" -// -// - python_package_prefix: optional. Dot-separated package prefix for $pythonRef. -// The last two path segments of the go_package ({service}/v{n}) are appended. -// Example: "temporalio.api" → "temporalio.api.workflowservice.v1.TypeName" -// If empty, $pythonRef is omitted. -// -// - typescript_package_prefix: optional. Scoped package prefix for $typescriptRef. -// The last two path segments of the go_package ({service}/v{n}) are appended. -// Example: "@temporalio/api" → "@temporalio/api/workflowservice/v1.TypeName" -// If empty, $typescriptRef is omitted. -// -// - include_operation_tags: optional, repeatable. Only include operations whose tags -// contain at least one of these values. If empty, all annotated operations are included -// (subject to exclude_operation_tags). Specify multiple times for multiple tags. -// Example: include_operation_tags=exposed -// -// - exclude_operation_tags: optional, repeatable. Exclude operations whose tags contain -// any of these values. Applied after include_operation_tags. -// Example: exclude_operation_tags=internal -type params struct { - nexusRpcLangsOut string - pythonPackagePrefix string - typescriptPackagePrefix string - includeOperationTags []string - excludeOperationTags []string -} - -// parseParams parses the comma-separated key=value parameter string provided by protoc. -func parseParams(raw string) (params, error) { - var p params - if raw == "" { - return p, nil - } - for kv := range strings.SplitSeq(raw, ",") { - key, value, ok := strings.Cut(kv, "=") - if !ok { - return p, fmt.Errorf("invalid parameter %q: expected key=value", kv) - } - switch key { - case "nexus-rpc_langs_out": - p.nexusRpcLangsOut = value - case "python_package_prefix": - p.pythonPackagePrefix = value - case "typescript_package_prefix": - p.typescriptPackagePrefix = value - case "include_operation_tags": - p.includeOperationTags = append(p.includeOperationTags, value) - case "exclude_operation_tags": - p.excludeOperationTags = append(p.excludeOperationTags, value) - default: - return p, fmt.Errorf("unknown parameter %q", key) - } - } - return p, nil -} - -// shouldIncludeOperation returns true if the method's nexus operation tags pass -// the include/exclude filters. Mirrors the logic from protoc-gen-go-nexus: -// 1. Method must have the nexus operation extension set. -// 2. If includeOperationTags is non-empty, at least one of the method's tags must match. -// 3. If excludeOperationTags is non-empty, none of the method's tags may match. -func shouldIncludeOperation(p params, m *protogen.Method) bool { - opts, ok := m.Desc.Options().(*descriptorpb.MethodOptions) - if !ok || opts == nil { - return false - } - if !proto.HasExtension(opts, nexusannotationsv1.E_Operation) { - return false - } - tags := proto.GetExtension(opts, nexusannotationsv1.E_Operation).(*nexusannotationsv1.OperationOptions).GetTags() - if len(p.includeOperationTags) > 0 && !slices.ContainsFunc(p.includeOperationTags, func(t string) bool { - return slices.Contains(tags, t) - }) { - return false - } - return !slices.ContainsFunc(p.excludeOperationTags, func(t string) bool { - return slices.Contains(tags, t) - }) -} - -func generate(gen *protogen.Plugin) error { - p, err := parseParams(gen.Request.GetParameter()) - if err != nil { - return err - } - - langsDoc := newDoc() - hasOps := false - - for _, f := range gen.Files { - if !f.Generate { - continue - } - for _, svc := range f.Services { - for _, m := range svc.Methods { - if !shouldIncludeOperation(p, m) { - continue - } - svcName := string(svc.Desc.Name()) - methodName := string(m.Desc.Name()) - hasOps = true - addOperation(langsDoc, svcName, methodName, - langRefs(p, f.Desc, m.Input.Desc), - langRefs(p, f.Desc, m.Output.Desc), - ) - } - } - } - - if !hasOps { - return nil - } - if p.nexusRpcLangsOut != "" { - return writeFile(gen, p.nexusRpcLangsOut, langsDoc) - } - return nil -} - -// langRefs builds the map of language-specific type refs for a message. -// -// Go, Java, dotnet, and Ruby refs are derived from proto file-level package options. -// Python and TypeScript refs require the corresponding prefix params to be set; if -// empty they are omitted. Both use the last two path segments of go_package -// ({service}/v{n}), dropping any intermediate grouping directory. -func langRefs(p params, file protoreflect.FileDescriptor, msg protoreflect.MessageDescriptor) map[string]string { - opts, ok := file.Options().(*descriptorpb.FileOptions) - if !ok || opts == nil { - return nil - } - name := string(msg.Name()) - refs := make(map[string]string) - - if pkg := opts.GetGoPackage(); pkg != "" { - // strip the ";alias" suffix (e.g. "go.temporal.io/api/workflowservice/v1;workflowservice") - pkg = strings.SplitN(pkg, ";", 2)[0] - refs["$goRef"] = pkg + "." + name - - segments := strings.Split(pkg, "/") - if len(segments) >= 2 { - tail := segments[len(segments)-2] + "/" + segments[len(segments)-1] - if p.pythonPackagePrefix != "" { - dotTail := strings.ReplaceAll(tail, "/", ".") - refs["$pythonRef"] = p.pythonPackagePrefix + "." + dotTail + "." + name - } - if p.typescriptPackagePrefix != "" { - refs["$typescriptRef"] = p.typescriptPackagePrefix + "/" + tail + "." + name - } - } - } - if pkg := opts.GetJavaPackage(); pkg != "" { - refs["$javaRef"] = pkg + "." + name - } - if pkg := opts.GetRubyPackage(); pkg != "" { - refs["$rubyRef"] = pkg + "::" + name - } - if pkg := opts.GetCsharpNamespace(); pkg != "" { - refs["$dotnetRef"] = pkg + "." + name - } - if len(refs) == 0 { - return nil - } - return refs -} - -// newDoc creates a yaml.Node document with the "nexusrpc: 1.0.0" header -// and an empty "services" mapping node. -func newDoc() *yaml.Node { - doc := &yaml.Node{Kind: yaml.DocumentNode} - root := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - doc.Content = []*yaml.Node{root} - root.Content = append(root.Content, - scalarNode("nexusrpc"), - scalarNode("1.0.0"), - scalarNode("services"), - &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}, - ) - return doc -} - -// servicesNode returns the "services" mapping node from a doc created by newDoc. -func servicesNode(doc *yaml.Node) *yaml.Node { - root := doc.Content[0] - for i := 0; i < len(root.Content)-1; i += 2 { - if root.Content[i].Value == "services" { - return root.Content[i+1] - } - } - panic("services node not found") -} - -// addOperation inserts a service → operation → {input, output} entry into doc. -// Services and operations are inserted in the order first encountered. -func addOperation(doc *yaml.Node, svcName, methodName string, input, output map[string]string) { - svcs := servicesNode(doc) - - var svcOps *yaml.Node - for i := 0; i < len(svcs.Content)-1; i += 2 { - if svcs.Content[i].Value == svcName { - svcMap := svcs.Content[i+1] - for j := 0; j < len(svcMap.Content)-1; j += 2 { - if svcMap.Content[j].Value == "operations" { - svcOps = svcMap.Content[j+1] - } - } - } - } - if svcOps == nil { - svcMap := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - svcOps = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - svcMap.Content = append(svcMap.Content, scalarNode("operations"), svcOps) - svcs.Content = append(svcs.Content, scalarNode(svcName), svcMap) - } - - opNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - if len(input) > 0 { - opNode.Content = append(opNode.Content, scalarNode("input"), mapNode(input)) - } - if len(output) > 0 { - opNode.Content = append(opNode.Content, scalarNode("output"), mapNode(output)) - } - svcOps.Content = append(svcOps.Content, scalarNode(methodName), opNode) -} - -// mapNode serializes a map[string]string as a yaml mapping node with keys in sorted order. -func mapNode(m map[string]string) *yaml.Node { - keys := make([]string, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - for _, k := range keys { - node.Content = append(node.Content, scalarNode(k), scalarNode(m[k])) - } - return node -} - -func scalarNode(value string) *yaml.Node { - return &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: value} -} - -func writeFile(gen *protogen.Plugin, name string, doc *yaml.Node) error { - f := gen.NewGeneratedFile(name, "") - enc := yaml.NewEncoder(f) - enc.SetIndent(2) - if err := enc.Encode(doc); err != nil { - return err - } - return enc.Close() -} diff --git a/cmd/protoc-gen-nexus-rpc-yaml/go.mod b/cmd/protoc-gen-nexus-rpc-yaml/go.mod deleted file mode 100644 index 863c771a6..000000000 --- a/cmd/protoc-gen-nexus-rpc-yaml/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -module github.com/temporalio/api/cmd/protoc-gen-nexus-rpc-yaml - -go 1.25.4 - -require ( - google.golang.org/protobuf v1.36.1 - gopkg.in/yaml.v3 v3.0.1 -) - -require github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 - -require github.com/google/go-cmp v0.6.0 // indirect diff --git a/cmd/protoc-gen-nexus-rpc-yaml/go.sum b/cmd/protoc-gen-nexus-rpc-yaml/go.sum deleted file mode 100644 index cbc5252ff..000000000 --- a/cmd/protoc-gen-nexus-rpc-yaml/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 h1:SWHt3Coj0VvF0Km1A0wlY+IjnHKsjQLgO29io84r3wY= -github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/protoc-gen-nexus-rpc-yaml/main.go b/cmd/protoc-gen-nexus-rpc-yaml/main.go deleted file mode 100644 index 31cdca92f..000000000 --- a/cmd/protoc-gen-nexus-rpc-yaml/main.go +++ /dev/null @@ -1,13 +0,0 @@ -// protoc-gen-nexus-rpc-yaml is a protoc plugin that generates nexus/temporal-proto-models-nexusrpc.yaml -// from proto service methods annotated with option (nexusannotations.v1.operation).tags = "exposed". -package main - -import ( - "google.golang.org/protobuf/compiler/protogen" -) - -func main() { - protogen.Options{}.Run(func(gen *protogen.Plugin) error { - return generate(gen) - }) -} diff --git a/cmd/protoc-gen-system-nexus-wit/generator.go b/cmd/protoc-gen-system-nexus-wit/generator.go new file mode 100644 index 000000000..31fdfbb43 --- /dev/null +++ b/cmd/protoc-gen-system-nexus-wit/generator.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "slices" + "strings" + + nexusannotationsv1 "github.com/nexus-rpc/nexus-proto-annotations/go/nexusannotations/v1" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/descriptorpb" +) + +type params struct { + nexusAPIGen string + output string + input string +} + +// parseParams parses the comma-separated key=value parameter string provided by protoc. +// +// - output: required. Path to the WIT file to generate, relative to the +// --system-nexus-wit_out directory. Example: "nexus/temporal-system.wit". +// +// - nexus_api_gen: optional. Path to the nexus-api-gen binary. Defaults to +// "nexus-api-gen". +// +// - input: optional. Existing WIT file to update. Defaults to output, so +// existing handwritten annotations and type refinements are preserved when +// regenerating in place. +func parseParams(raw string) (params, error) { + p := params{ + nexusAPIGen: "nexus-api-gen", + } + if raw == "" { + return p, nil + } + for kv := range strings.SplitSeq(raw, ",") { + key, value, ok := strings.Cut(kv, "=") + if !ok { + return p, fmt.Errorf("invalid parameter %q: expected key=value", kv) + } + switch key { + case "nexus_api_gen": + p.nexusAPIGen = value + case "output": + p.output = value + case "input": + p.input = value + default: + return p, fmt.Errorf("unknown parameter %q", key) + } + } + return p, nil +} + +func generate(gen *protogen.Plugin) error { + p, err := parseParams(gen.Request.GetParameter()) + if err != nil { + return err + } + if p.output == "" { + return fmt.Errorf("missing required output parameter") + } + if p.input == "" { + p.input = p.output + } + + rpcs := exposedRPCs(gen) + if len(rpcs) == 0 { + return fmt.Errorf("no proto RPCs are marked as exposed Nexus operations") + } + + tempDir, err := os.MkdirTemp("", "system-nexus-wit-*") + if err != nil { + return err + } + defer os.RemoveAll(tempDir) + + descriptorPath := filepath.Join(tempDir, "temporal_api.bin") + if err := writeDescriptorSet(gen, descriptorPath); err != nil { + return err + } + + tempOutput := filepath.Join(tempDir, "system-nexus.wit") + input := "" + if _, err := os.Stat(p.input); err == nil { + if err := copyFile(p.input, tempOutput); err != nil { + return err + } + input = tempOutput + } else if !os.IsNotExist(err) { + return err + } + + for _, rpc := range rpcs { + if err := runAddRPC(p.nexusAPIGen, descriptorPath, rpc, tempOutput, input); err != nil { + return err + } + input = tempOutput + } + + content, err := os.ReadFile(tempOutput) + if err != nil { + return err + } + _, err = gen.NewGeneratedFile(p.output, "").Write(content) + return err +} + +func exposedRPCs(gen *protogen.Plugin) []string { + var rpcs []string + for _, f := range gen.Files { + if !f.Generate { + continue + } + for _, svc := range f.Services { + for _, m := range svc.Methods { + if isExposedOperation(m) { + rpcs = append(rpcs, string(m.Desc.FullName())) + } + } + } + } + return rpcs +} + +func isExposedOperation(m *protogen.Method) bool { + opts, ok := m.Desc.Options().(*descriptorpb.MethodOptions) + if !ok || opts == nil { + return false + } + if !proto.HasExtension(opts, nexusannotationsv1.E_Operation) { + return false + } + tags := proto.GetExtension(opts, nexusannotationsv1.E_Operation).(*nexusannotationsv1.OperationOptions).GetTags() + return slices.Contains(tags, "exposed") +} + +func writeDescriptorSet(gen *protogen.Plugin, descriptorPath string) error { + data, err := proto.Marshal(&descriptorpb.FileDescriptorSet{ + File: gen.Request.GetProtoFile(), + }) + if err != nil { + return err + } + return os.WriteFile(descriptorPath, data, 0o644) +} + +func runAddRPC(nexusAPIGen string, descriptors string, rpc string, output string, input string) error { + args := []string{ + "add-rpc", + "--descriptors", descriptors, + "--rpc", rpc, + "--output", output, + } + if input != "" { + args = append(args, "--input", input) + } + + command := exec.Command(nexusAPIGen, args...) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + if err := command.Run(); err != nil { + return fmt.Errorf("%s %s: %w", nexusAPIGen, strings.Join(args, " "), err) + } + return nil +} + +func copyFile(source string, destination string) error { + content, err := os.ReadFile(source) + if err != nil { + return err + } + return os.WriteFile(destination, content, 0o644) +} diff --git a/cmd/protoc-gen-system-nexus-wit/go.mod b/cmd/protoc-gen-system-nexus-wit/go.mod new file mode 100644 index 000000000..222e32187 --- /dev/null +++ b/cmd/protoc-gen-system-nexus-wit/go.mod @@ -0,0 +1,8 @@ +module github.com/temporalio/api/cmd/protoc-gen-system-nexus-wit + +go 1.25.4 + +require ( + github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 + google.golang.org/protobuf v1.36.1 +) diff --git a/cmd/protoc-gen-system-nexus-wit/go.sum b/cmd/protoc-gen-system-nexus-wit/go.sum new file mode 100644 index 000000000..c96f6a1c5 --- /dev/null +++ b/cmd/protoc-gen-system-nexus-wit/go.sum @@ -0,0 +1,8 @@ +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 h1:SWHt3Coj0VvF0Km1A0wlY+IjnHKsjQLgO29io84r3wY= +github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/cmd/protoc-gen-system-nexus-wit/main.go b/cmd/protoc-gen-system-nexus-wit/main.go new file mode 100644 index 000000000..0e9fe3182 --- /dev/null +++ b/cmd/protoc-gen-system-nexus-wit/main.go @@ -0,0 +1,11 @@ +// protoc-gen-system-nexus-wit generates nexus/temporal-system.wit from proto service +// methods annotated with option (nexusannotations.v1.operation).tags = "exposed". +package main + +import "google.golang.org/protobuf/compiler/protogen" + +func main() { + protogen.Options{}.Run(func(gen *protogen.Plugin) error { + return generate(gen) + }) +} diff --git a/nexus/temporal-proto-models-nexusrpc.yaml b/nexus/temporal-proto-models-nexusrpc.yaml deleted file mode 100644 index e0761fd15..000000000 --- a/nexus/temporal-proto-models-nexusrpc.yaml +++ /dev/null @@ -1,19 +0,0 @@ -nexusrpc: 1.0.0 -services: - WorkflowService: - operations: - SignalWithStartWorkflowExecution: - input: - $dotnetRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionRequest - $goRef: go.temporal.io/api/workflowservice/v1.SignalWithStartWorkflowExecutionRequest - $javaRef: io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest - $pythonRef: temporalio.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest - $rubyRef: Temporalio::Api::WorkflowService::V1::SignalWithStartWorkflowExecutionRequest - $typescriptRef: '@temporalio/api/workflowservice/v1.SignalWithStartWorkflowExecutionRequest' - output: - $dotnetRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionResponse - $goRef: go.temporal.io/api/workflowservice/v1.SignalWithStartWorkflowExecutionResponse - $javaRef: io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse - $pythonRef: temporalio.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse - $rubyRef: Temporalio::Api::WorkflowService::V1::SignalWithStartWorkflowExecutionResponse - $typescriptRef: '@temporalio/api/workflowservice/v1.SignalWithStartWorkflowExecutionResponse' diff --git a/nexus/temporal-system.wit b/nexus/temporal-system.wit new file mode 100644 index 000000000..e410c64d7 --- /dev/null +++ b/nexus/temporal-system.wit @@ -0,0 +1,78 @@ +package temporal:nexus@1.0.0; + +world system { + export workflow-service; +} + +/// @nexus.endpoint "__temporal_system" +interface workflow-service { + use nexus:temporal-types/model@1.0.0.{ + duration, + memo, + payloads, + placeholder, + priority, + retry-policy, + search-attributes, + signal-function, + task-queue, + user-metadata, + versioning-override, + workflow-function, + workflow-id-conflict-policy, + workflow-id-reuse-policy, + }; + + /// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest" + record signal-with-start-workflow-request { + /// @nexus.proto-field "workflow_type" + workflow: workflow-function, + workflow-id: string, + task-queue: task-queue, + /// @nexus.proto-field "signal_name" + signal: signal-function, + workflow-execution-timeout: option, + workflow-run-timeout: option, + workflow-task-timeout: option, + identity: option, + request-id: option, + workflow-id-reuse-policy: option, + workflow-id-conflict-policy: option, + retry-policy: option, + cron-schedule: option, + memo: option, + search-attributes: option, + priority: option, + versioning-override: option, + workflow-start-delay: option, + user-metadata: option, + /// @nexus.source python="workflow_namespace" typescript="workflowNamespace" + namespace: string, + /// @nexus.omit + control: placeholder, + /// @nexus.omit + header: placeholder, + /// @nexus.omit + links: placeholder, + /// @nexus.omit + time-skipping-config: placeholder, + } + + /// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse" + record signal-with-start-workflow-response { + run-id: option, + started: option, + /// @nexus.omit + signal-link: placeholder, + } + + /// @nexus.output-transform + /// python-type="workflow.ExternalWorkflowHandle[typing.Any]" + /// python="workflow.get_external_workflow_handle(request.workflow_id, run_id=result.run_id)" + /// typescript-type="workflow.ExternalWorkflowHandle" + /// typescript="workflow.getExternalWorkflowHandle(request.workflowId, result.runId ?? undefined)" + /// @nexus.operation name="SignalWithStartWorkflowExecution" + signal-with-start-workflow: func( + request: signal-with-start-workflow-request, + ) -> signal-with-start-workflow-response; +}