diff --git a/.rubocop.yml b/.rubocop.yml index b139eeb4..d3492a37 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -56,3 +56,7 @@ Metrics/ParameterLists: # We want our require lists to be in order Style/RequireOrder: Enabled: true + +# Feature files may have many classes in the file +Style/OneClassPerFile: + Enabled: false diff --git a/features/go.mod b/features/go.mod index e1dd87b0..23a314de 100644 --- a/features/go.mod +++ b/features/go.mod @@ -6,12 +6,13 @@ toolchain go1.24.3 require ( github.com/google/uuid v1.6.0 + github.com/nexus-rpc/sdk-go v0.5.1 github.com/stretchr/testify v1.10.0 github.com/temporalio/features/harness/go v0.0.0-00010101000000-000000000000 github.com/uber-go/tally/v4 v4.1.1 github.com/urfave/cli/v2 v2.3.0 - go.temporal.io/api v1.60.0 - go.temporal.io/sdk v1.37.0 + go.temporal.io/api v1.62.1 + go.temporal.io/sdk v1.40.0 go.temporal.io/sdk/contrib/tally v0.2.0 golang.org/x/mod v0.17.0 google.golang.org/grpc v1.67.1 diff --git a/features/go.sum b/features/go.sum index 728eba19..8c4b561c 100644 --- a/features/go.sum +++ b/features/go.sum @@ -116,8 +116,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/sdk-go v0.3.0 h1:Y3B0kLYbMhd4C2u00kcYajvmOrfozEtTV/nHSnV57jA= -github.com/nexus-rpc/sdk-go v0.3.0/go.mod h1:TpfkM2Cw0Rlk9drGkoiSMpFqflKTiQLWUNyKJjF8mKQ= +github.com/nexus-rpc/sdk-go v0.5.1 h1:UFYYfoHlQc+Pn9gQpmn9QE7xluewAn2AO1OSkAh7YFU= +github.com/nexus-rpc/sdk-go v0.5.1/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -182,11 +182,11 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.temporal.io/api v1.5.0/go.mod h1:BqKxEJJYdxb5dqf0ODfzfMxh8UEQ5L3zKS51FiIYYkA= -go.temporal.io/api v1.60.0 h1:SlRkizt3PXu/J62NWlUNLldHtJhUxfsBRuF4T0KYkgY= -go.temporal.io/api v1.60.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.62.1 h1:7UHMNOIqfYBVTaW0JIh/wDpw2jORkB6zUKsxGtvjSZU= +go.temporal.io/api v1.62.1/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.12.0/go.mod h1:lSp3lH1lI0TyOsus0arnO3FYvjVXBZGi/G7DjnAnm6o= -go.temporal.io/sdk v1.37.0 h1:RbwCkUQuqY4rfCzdrDZF9lgT7QWG/pHlxfZFq0NPpDQ= -go.temporal.io/sdk v1.37.0/go.mod h1:tOy6vGonfAjrpCl6Bbw/8slTgQMiqvoyegRv2ZHPm5M= +go.temporal.io/sdk v1.40.0 h1:n9JN3ezVpWBxLzz5xViCo0sKxp7kVVhr1Su0bcMRNNs= +go.temporal.io/sdk v1.40.0/go.mod h1:tauxVfN174F0bdEs27+i0h8UPD7xBb6Py2SPHo7f1C0= go.temporal.io/sdk/contrib/tally v0.2.0 h1:XnTJIQcjOv+WuCJ1u8Ve2nq+s2H4i/fys34MnWDRrOo= go.temporal.io/sdk/contrib/tally v0.2.0/go.mod h1:1kpSuCms/tHeJQDPuuKkaBsMqfHnIIRnCtUYlPNXxuE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= diff --git a/features/snippets/plugins/plugins.cs b/features/snippets/plugins/plugins.cs new file mode 100644 index 00000000..a3f6de86 --- /dev/null +++ b/features/snippets/plugins/plugins.cs @@ -0,0 +1,107 @@ +using NexusRpc; +using NexusRpc.Handlers; +using Temporalio.Activities; +using Temporalio.Api.Common.V1; +using Temporalio.Client.Interceptors; +using Temporalio.Common; +using Temporalio.Converters; +using Temporalio.Nexus; +using Temporalio.Worker.Interceptors; +using Temporalio.Workflows; + +class PluginsSnippet +{ + // @@@SNIPSTART dotnet-plugins-activity + [Activity] + static void SomeActivity() => throw new NotImplementedException(); + + SimplePlugin activityPlugin = new SimplePlugin( + "PluginName", + new SimplePluginOptions() { }.AddActivity(SomeActivity)); + // @@@SNIPEND + + // @@@SNIPSTART dotnet-plugins-workflow + [Workflow] + class SimpleWorkflow + { + [WorkflowRun] + public Task RunAsync(string name) => Task.FromResult($"Hello, {name}!"); + } + + SimplePlugin workflowPlugin = new SimplePlugin( + "PluginName", + new SimplePluginOptions() { }.AddWorkflow()); + // @@@SNIPEND + + // @@@SNIPSTART dotnet-plugins-nexus + [NexusService] + public interface IStringService + { + [NexusOperation] + string DoSomething(string name); + } + + [NexusServiceHandler(typeof(IStringService))] + public class HandlerFactoryStringService + { + private readonly Func> handlerFactory; + + public HandlerFactoryStringService(Func> handlerFactory) => + this.handlerFactory = handlerFactory; + + [NexusOperationHandler] + public IOperationHandler DoSomething() => handlerFactory(); + } + + SimplePlugin nexusPlugin = new SimplePlugin( + "PluginName", + new SimplePluginOptions() { }.AddNexusService(new HandlerFactoryStringService(() => + OperationHandler.Sync((ctx, name) => $"Hello, {name}"))) + ); + // @@@SNIPEND + + // @@@SNIPSTART dotnet-plugins-converter + private class Codec : IPayloadCodec + { + public Task> EncodeAsync(IReadOnlyCollection payloads) => throw new NotImplementedException(); + public Task> DecodeAsync(IReadOnlyCollection payloads) => throw new NotImplementedException(); + } + + SimplePlugin converterPlugin = new SimplePlugin( + "PluginName", + new SimplePluginOptions() + { + DataConverterOption = new SimplePluginOptions.SimplePluginOption( + (converter) => converter with { PayloadCodec = new Codec() } + ), + }); + // @@@SNIPEND + + // @@@SNIPSTART dotnet-plugins-interceptors + private class SomeClientInterceptor : IClientInterceptor + { + public ClientOutboundInterceptor InterceptClient( + ClientOutboundInterceptor nextInterceptor) => + throw new NotImplementedException(); + } + + private class SomeWorkerInterceptor : IWorkerInterceptor + { + public WorkflowInboundInterceptor InterceptWorkflow( + WorkflowInboundInterceptor nextInterceptor) => + throw new NotImplementedException(); + + public ActivityInboundInterceptor InterceptActivity( + ActivityInboundInterceptor nextInterceptor) => + throw new NotImplementedException(); + } + + SimplePlugin interceptorPlugin = new SimplePlugin( + "PluginName", + new SimplePluginOptions() + { + ClientInterceptors = new List() { new SomeClientInterceptor() }, + WorkerInterceptors = new List() { new SomeWorkerInterceptor() }, + }); + // @@@SNIPEND +} diff --git a/features/snippets/plugins/plugins.go b/features/snippets/plugins/plugins.go new file mode 100644 index 00000000..4a0770cf --- /dev/null +++ b/features/snippets/plugins/plugins.go @@ -0,0 +1,120 @@ +package plugins + +import ( + "context" + + "github.com/nexus-rpc/sdk-go/nexus" + "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/converter" + "go.temporal.io/sdk/interceptor" + "go.temporal.io/sdk/temporal" + "go.temporal.io/sdk/workflow" +) + +// @@@SNIPSTART go-plugin-activity +func SomeActivity(ctx context.Context) error { + // Activity implementation + return nil +} + +func createActivityPlugin() (*temporal.SimplePlugin, error) { + return temporal.NewSimplePlugin(temporal.SimplePluginOptions{ + Name: "PluginName", + RunContextBefore: func(ctx context.Context, options temporal.SimplePluginRunContextBeforeOptions) error { + options.Registry.RegisterActivityWithOptions( + SomeActivity, + activity.RegisterOptions{Name: "SomeActivity"}, + ) + return nil + }, + }) +} + +// @@@SNIPEND + +// @@@SNIPSTART go-plugin-workflow +func HelloWorkflow(ctx workflow.Context, name string) (string, error) { + return "Hello, " + name + "!", nil +} + +func createWorkflowPlugin() (*temporal.SimplePlugin, error) { + return temporal.NewSimplePlugin(temporal.SimplePluginOptions{ + Name: "PluginName", + RunContextBefore: func(ctx context.Context, options temporal.SimplePluginRunContextBeforeOptions) error { + options.Registry.RegisterWorkflowWithOptions( + HelloWorkflow, + workflow.RegisterOptions{Name: "HelloWorkflow"}, + ) + return nil + }, + }) +} + +// @@@SNIPEND + +// @@@SNIPSTART go-plugin-nexus +type WeatherInput struct { + City string `json:"city"` +} + +type Weather struct { + City string `json:"city"` + TemperatureRange string `json:"temperatureRange"` + Conditions string `json:"conditions"` +} + +var WeatherService = nexus.NewService("weather-service") + +var GetWeatherOperation = nexus.NewSyncOperation( + "get-weather", + func(ctx context.Context, input WeatherInput, options nexus.StartOperationOptions) (Weather, error) { + return Weather{ + City: input.City, + TemperatureRange: "14-20C", + Conditions: "Sunny with wind.", + }, nil + }, +) + +func createNexusPlugin() (*temporal.SimplePlugin, error) { + return temporal.NewSimplePlugin(temporal.SimplePluginOptions{ + Name: "PluginName", + RunContextBefore: func(ctx context.Context, options temporal.SimplePluginRunContextBeforeOptions) error { + options.Registry.RegisterNexusService(WeatherService) + return nil + }, + }) +} + +// @@@SNIPEND + +// @@@SNIPSTART go-plugin-converter +func createConverterPlugin() (*temporal.SimplePlugin, error) { + customConverter := converter.GetDefaultDataConverter() // Or your custom converter + + return temporal.NewSimplePlugin(temporal.SimplePluginOptions{ + Name: "PluginName", + DataConverter: customConverter, + }) +} + +// @@@SNIPEND + +// @@@SNIPSTART go-plugin-interceptors +type SomeWorkerInterceptor struct { + interceptor.WorkerInterceptorBase +} + +type SomeClientInterceptor struct { + interceptor.ClientInterceptorBase +} + +func createInterceptorPlugin() (*temporal.SimplePlugin, error) { + return temporal.NewSimplePlugin(temporal.SimplePluginOptions{ + Name: "PluginName", + WorkerInterceptors: []interceptor.WorkerInterceptor{&SomeWorkerInterceptor{}}, + ClientInterceptors: []interceptor.ClientInterceptor{&SomeClientInterceptor{}}, + }) +} + +// @@@SNIPEND \ No newline at end of file diff --git a/features/snippets/plugins/plugins.java b/features/snippets/plugins/plugins.java new file mode 100644 index 00000000..1e70e936 --- /dev/null +++ b/features/snippets/plugins/plugins.java @@ -0,0 +1,119 @@ +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; +import io.temporal.common.SimplePlugin; +import io.temporal.common.interceptors.WorkerInterceptorBase; +import io.temporal.common.interceptors.WorkflowClientInterceptorBase; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +class PluginsSnippet { + + // @@@SNIPSTART java-plugin-activity + @ActivityInterface + public interface SomeActivity { + @ActivityMethod + void someActivity(); + } + + public class SomeActivityImpl implements SomeActivity { + @Override + public void someActivity() { + // Activity implementation + } + } + + SimplePlugin activityPlugin = + SimplePlugin.newBuilder("PluginName") + .registerActivitiesImplementations(new SomeActivityImpl()) + .build(); + // @@@SNIPEND + + // @@@SNIPSTART java-plugin-workflow + @WorkflowInterface + public interface HelloWorkflow { + @WorkflowMethod + String run(String name); + } + + public static class HelloWorkflowImpl implements HelloWorkflow { + @Override + public String run(String name) { + return "Hello, " + name + "!"; + } + } + + SimplePlugin workflowPlugin = + SimplePlugin.newBuilder("PluginName") + .registerWorkflowImplementationTypes(HelloWorkflowImpl.class) + .build(); + // @@@SNIPEND + + // @@@SNIPSTART java-plugin-nexus + // Example Nexus service implementation + public class WeatherService { + public Weather getWeather(WeatherInput input) { + return new Weather(input.getCity(), "14-20C", "Sunny with wind."); + } + } + + public static class Weather { + private final String city; + private final String temperatureRange; + private final String conditions; + + public Weather(String city, String temperatureRange, String conditions) { + this.city = city; + this.temperatureRange = temperatureRange; + this.conditions = conditions; + } + + // Getters... + } + + public static class WeatherInput { + private final String city; + + public WeatherInput(String city) { + this.city = city; + } + + public String getCity() { + return city; + } + } + + SimplePlugin nexusPlugin = + SimplePlugin.newBuilder("PluginName") + .registerNexusServiceImplementation(new WeatherService()) + .build(); + // @@@SNIPEND + + // @@@SNIPSTART java-plugin-converter + SimplePlugin converterPlugin = + SimplePlugin.newBuilder("PluginName") + .customizeDataConverter( + existingConverter -> { + // Customize the data converter + // This example keeps the existing converter unchanged + // In practice, you might wrap it with additional functionality + return existingConverter; + }) + .build(); + // @@@SNIPEND + + // @@@SNIPSTART java-plugin-interceptors + public class SomeWorkerInterceptor extends WorkerInterceptorBase { + // Your worker interceptor implementation + } + + public class SomeClientInterceptor extends WorkflowClientInterceptorBase { + // Your client interceptor implementation + } + + SimplePlugin interceptorPlugin = + SimplePlugin.newBuilder("PluginName") + .addWorkerInterceptors(new SomeWorkerInterceptor()) + .addClientInterceptors(new SomeClientInterceptor()) + .build(); + // @@@SNIPEND +} diff --git a/features/snippets/plugins/plugins.py b/features/snippets/plugins/plugins.py new file mode 100644 index 00000000..3f4a8fba --- /dev/null +++ b/features/snippets/plugins/plugins.py @@ -0,0 +1,116 @@ +import dataclasses +from dataclasses import dataclass + +import nexusrpc +import temporalio.client +import temporalio.worker +from temporalio import activity, workflow +from temporalio.contrib.pydantic import pydantic_data_converter +from temporalio.converter import DataConverter +from temporalio.plugin import SimplePlugin +from temporalio.worker import WorkflowRunner +from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner + + +# @@@SNIPSTART python-plugin-activity +@activity.defn +async def some_activity() -> None: + return None + + +plugin = SimplePlugin("PluginName", activities=[some_activity]) +# @@@SNIPEND + + +# @@@SNIPSTART python-plugin-workflow +@workflow.defn +class HelloWorkflow: + @workflow.run + async def run(self, name: str) -> str: + return f"Hello, {name}!" + + +plugin = SimplePlugin("PluginName", workflows=[HelloWorkflow]) +# @@@SNIPEND + + +@dataclass +class Weather: + city: str + temperature_range: str + conditions: str + + +@dataclass +class WeatherInput: + city: str + + +# @@@SNIPSTART python-plugin-nexus +@nexusrpc.service +class WeatherService: + get_weather_nexus_operation: nexusrpc.Operation[WeatherInput, Weather] + + +@nexusrpc.handler.service_handler(service=WeatherService) +class WeatherServiceHandler: + @nexusrpc.handler.sync_operation + async def get_weather_nexus_operation( + self, ctx: nexusrpc.handler.StartOperationContext, input: WeatherInput + ) -> Weather: + return Weather( + city=input.city, + temperature_range="14-20C", + conditions="Sunny with wind.", + ) + + +plugin = SimplePlugin("PluginName", nexus_service_handlers=[WeatherServiceHandler()]) +# @@@SNIPEND + + +# @@@SNIPSTART python-plugin-converter +def set_converter(converter: DataConverter | None) -> DataConverter: + if converter is None or converter == DataConverter.default: + return pydantic_data_converter + # Should consider interactions with other plugins, + # as this will override the data converter. + # This may mean failing, warning, or something else + return converter + + +plugin = SimplePlugin("PluginName", data_converter=set_converter) +# @@@SNIPEND + + +# @@@SNIPSTART python-plugin-interceptors +class SomeWorkerInterceptor(temporalio.worker.Interceptor): + pass # Your implementation + + +class SomeClientInterceptor(temporalio.client.Interceptor): + pass # Your implementation + + +plugin = SimplePlugin( + "PluginName", interceptors=[SomeWorkerInterceptor(), SomeClientInterceptor()] +) +# @@@SNIPEND + + +# @@@SNIPSTART python-plugin-sandbox +def workflow_runner(runner: WorkflowRunner | None) -> WorkflowRunner: + if not runner: + raise ValueError("No WorkflowRunner provided to the plugin.") + + # If in sandbox, add additional passthrough + if isinstance(runner, SandboxedWorkflowRunner): + return dataclasses.replace( + runner, + restrictions=runner.restrictions.with_passthrough_modules("module"), + ) + return runner + + +plugin = SimplePlugin("PluginName", workflow_runner=workflow_runner) +# @@@SNIPEND diff --git a/features/snippets/plugins/plugins.rb b/features/snippets/plugins/plugins.rb new file mode 100644 index 00000000..8cc5583d --- /dev/null +++ b/features/snippets/plugins/plugins.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'temporalio/simple_plugin' +require 'temporalio/activity' +require 'temporalio/workflow' + +# @@@SNIPSTART ruby-plugin-activity +def some_activity + # Activity implementation +end + +plugin = Temporalio::SimplePlugin.new( + name: 'PluginName', + activities: [method(:some_activity)] +) +# @@@SNIPEND + +# @@@SNIPSTART ruby-plugin-workflow +class HelloWorkflow < Temporalio::Workflow::Definition + def execute(name) + "Hello, #{name}!" + end +end + +plugin = Temporalio::SimplePlugin.new( + name: 'PluginName', + workflows: [HelloWorkflow] +) +# @@@SNIPEND + +# @@@SNIPSTART ruby-plugin-converter +custom_converter = Temporalio::Converters::DataConverter.new( + payload_converter: Temporalio::Converters::PayloadConverter.default +) + +plugin = Temporalio::SimplePlugin.new( + name: 'PluginName', + data_converter: custom_converter +) +# @@@SNIPEND + +# @@@SNIPSTART ruby-plugin-interceptors +class SomeWorkerInterceptor + include Temporalio::Worker::Interceptor::Workflow + + def intercept_workflow(next_interceptor) + # Your interceptor implementation + next_interceptor + end +end + +class SomeClientInterceptor + include Temporalio::Client::Interceptor + + def intercept_client(next_interceptor) + # Your interceptor implementation + next_interceptor + end +end + +plugin = Temporalio::SimplePlugin.new( + name: 'PluginName', + client_interceptors: [SomeClientInterceptor.new], + worker_interceptors: [SomeWorkerInterceptor.new] +) +# @@@SNIPEND \ No newline at end of file diff --git a/features/snippets/plugins/plugins.ts b/features/snippets/plugins/plugins.ts new file mode 100644 index 00000000..5212be26 --- /dev/null +++ b/features/snippets/plugins/plugins.ts @@ -0,0 +1,88 @@ +import * as nexus from 'nexus-rpc'; +import { Context } from '@temporalio/activity'; +import { SimplePlugin } from '@temporalio/plugin'; +import { DataConverter, PayloadCodec, Payload } from '@temporalio/common'; +import { WorkflowClientInterceptor } from '@temporalio/client'; +import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } from '@temporalio/worker'; +/* eslint-disable @typescript-eslint/no-unused-vars */ + +{ + // @@@SNIPSTART typescript-plugins-activity + const activity = async () => 'activity'; + const plugin = new SimplePlugin({ + name: 'plugin-name', + activities: { + pluginActivity: activity, + }, + }); + // @@@SNIPEND +} +{ + // @@@SNIPSTART typescript-plugins-nexus + const testServiceHandler = nexus.serviceHandler( + nexus.service('testService', { + testSyncOp: nexus.operation(), + }), + { + async testSyncOp(_, input) { + return input; + }, + }, + ); + const plugin = new SimplePlugin({ + name: 'plugin-name', + nexusServices: [testServiceHandler], + }); + // @@@SNIPEND +} + +{ + // @@@SNIPSTART typescript-plugins-converter + const codec: PayloadCodec = { + encode(_payloads: Payload[]): Promise { + throw new Error(); + }, + decode(_payloads: Payload[]): Promise { + throw new Error(); + }, + }; + const plugin = new SimplePlugin({ + name: 'plugin-name', + dataConverter: (converter: DataConverter | undefined) => ({ + ...converter, + payloadCodecs: [...(converter?.payloadCodecs ?? []), codec], + }), + }); + // @@@SNIPEND +} + +{ + // @@@SNIPSTART typescript-plugins-interceptors + class MyWorkflowClientInterceptor implements WorkflowClientInterceptor {} + + class MyActivityInboundInterceptor implements ActivityInboundCallsInterceptor {} + + class MyActivityOutboundInterceptor implements ActivityOutboundCallsInterceptor {} + + const workflowInterceptorsPath = ''; + + const plugin = new SimplePlugin({ + name: 'plugin-name', + clientInterceptors: { + workflow: [new MyWorkflowClientInterceptor()], + }, + workerInterceptors: { + client: { + workflow: [new MyWorkflowClientInterceptor()], + }, + workflowModules: [workflowInterceptorsPath], + activity: [ + (_: Context) => ({ + inbound: new MyActivityInboundInterceptor(), + outbound: new MyActivityOutboundInterceptor(), + }), + ], + }, + }); + // @@@SNIPEND +} diff --git a/features/snippets/worker/worker.cs b/features/snippets/worker/worker.cs new file mode 100644 index 00000000..5d0f77a4 --- /dev/null +++ b/features/snippets/worker/worker.cs @@ -0,0 +1,19 @@ +using Temporalio.Client; +using Temporalio.Worker; + +public class WorkerSnippet +{ + public static async Task Run() + { + var client = await TemporalClient.ConnectAsync(new("localhost:7233")); + + // @@@SNIPSTART dotnet-worker-max-cached-workflows + using var worker = new TemporalWorker( + client, + new TemporalWorkerOptions("task-queue") + { + MaxCachedWorkflows = 0 + }); + // @@@SNIPEND + } +} diff --git a/features/snippets/worker/worker.go b/features/snippets/worker/worker.go new file mode 100644 index 00000000..bfaa6fd8 --- /dev/null +++ b/features/snippets/worker/worker.go @@ -0,0 +1,23 @@ +package worker + +import ( + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" +) + +func Run() error { + c, err := client.Dial(client.Options{ + HostPort: "localhost:7233", + }) + if err != nil { + return err + } + defer c.Close() + + // @@@SNIPSTART go-worker-max-cached-workflows + worker.SetStickyWorkflowCacheSize(0) + w := worker.New(c, "task-queue", worker.Options{}) + // @@@SNIPEND + + return w.Run(worker.InterruptCh()) +} \ No newline at end of file diff --git a/features/snippets/worker/worker.java b/features/snippets/worker/worker.java new file mode 100644 index 00000000..3a3ee8c4 --- /dev/null +++ b/features/snippets/worker/worker.java @@ -0,0 +1,21 @@ +import io.temporal.client.WorkflowClient; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkerFactoryOptions; + +class WorkerSnippet { + public static void main(String[] args) { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = WorkflowClient.newInstance(service); + + // @@@SNIPSTART java-worker-max-cached-workflows + WorkerFactory factory = + WorkerFactory.newInstance( + client, WorkerFactoryOptions.newBuilder().setWorkflowCacheSize(0).build()); + Worker worker = factory.newWorker("task-queue"); + // @@@SNIPEND + + factory.start(); + } +} diff --git a/features/snippets/worker/worker.py b/features/snippets/worker/worker.py new file mode 100644 index 00000000..dbaf038f --- /dev/null +++ b/features/snippets/worker/worker.py @@ -0,0 +1,12 @@ +from temporalio.client import Client +from temporalio.worker import Worker + + +async def run(): + client = await Client.connect( + "localhost:7233", + ) + # @@@SNIPSTART python-worker-max-cached-workflows + worker = Worker(client, task_queue="task-queue", max_cached_workflows=0) + # @@@SNIPEND + await worker.run() diff --git a/features/snippets/worker/worker.rb b/features/snippets/worker/worker.rb new file mode 100644 index 00000000..f4ab1f5e --- /dev/null +++ b/features/snippets/worker/worker.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'temporalio/client' +require 'temporalio/worker' + +def run + client = Temporalio::Client.connect( + 'localhost:7233', + 'default' + ) + + # @@@SNIPSTART ruby-worker-max-cached-workflows + worker = Temporalio::Worker.new( + client: client, + task_queue: 'task-queue', + max_cached_workflows: 0 + ) + # @@@SNIPEND + + worker.run +end \ No newline at end of file diff --git a/features/snippets/worker/worker.ts b/features/snippets/worker/worker.ts new file mode 100644 index 00000000..a725b4fa --- /dev/null +++ b/features/snippets/worker/worker.ts @@ -0,0 +1,16 @@ +import { NativeConnection, Worker } from '@temporalio/worker'; +/* eslint-disable @typescript-eslint/no-unused-vars */ + +async function _run() { + const connection = await NativeConnection.connect({ + address: 'localhost:7233', + }); + + // @@@SNIPSTART typescript-worker-max-cached-workflows + const worker = await Worker.create({ + connection, + taskQueue: 'task-queue', + maxCachedWorkflows: 0, + }); + // @@@SNIPEND +} diff --git a/go.mod b/go.mod index b3af00c9..91d3f4b2 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/temporalio/features/features v0.0.0-00010101000000-000000000000 github.com/temporalio/features/harness/go v0.0.0-00010101000000-000000000000 github.com/urfave/cli/v2 v2.25.7 - go.temporal.io/sdk v1.37.0 + go.temporal.io/sdk v1.40.0 golang.org/x/mod v0.17.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -23,7 +23,7 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/nexus-rpc/sdk-go v0.3.0 // indirect + github.com/nexus-rpc/sdk-go v0.5.1 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -31,7 +31,7 @@ require ( github.com/twmb/murmur3 v1.1.8 // indirect github.com/uber-go/tally/v4 v4.1.7 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - go.temporal.io/api v1.60.0 // indirect + go.temporal.io/api v1.62.1 // indirect go.temporal.io/sdk/contrib/tally v0.2.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 1d17da0b..b0e3ca47 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/sdk-go v0.3.0 h1:Y3B0kLYbMhd4C2u00kcYajvmOrfozEtTV/nHSnV57jA= -github.com/nexus-rpc/sdk-go v0.3.0/go.mod h1:TpfkM2Cw0Rlk9drGkoiSMpFqflKTiQLWUNyKJjF8mKQ= +github.com/nexus-rpc/sdk-go v0.5.1 h1:UFYYfoHlQc+Pn9gQpmn9QE7xluewAn2AO1OSkAh7YFU= +github.com/nexus-rpc/sdk-go v0.5.1/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -183,11 +183,11 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.temporal.io/api v1.5.0/go.mod h1:BqKxEJJYdxb5dqf0ODfzfMxh8UEQ5L3zKS51FiIYYkA= -go.temporal.io/api v1.60.0 h1:SlRkizt3PXu/J62NWlUNLldHtJhUxfsBRuF4T0KYkgY= -go.temporal.io/api v1.60.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.62.1 h1:7UHMNOIqfYBVTaW0JIh/wDpw2jORkB6zUKsxGtvjSZU= +go.temporal.io/api v1.62.1/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.12.0/go.mod h1:lSp3lH1lI0TyOsus0arnO3FYvjVXBZGi/G7DjnAnm6o= -go.temporal.io/sdk v1.37.0 h1:RbwCkUQuqY4rfCzdrDZF9lgT7QWG/pHlxfZFq0NPpDQ= -go.temporal.io/sdk v1.37.0/go.mod h1:tOy6vGonfAjrpCl6Bbw/8slTgQMiqvoyegRv2ZHPm5M= +go.temporal.io/sdk v1.40.0 h1:n9JN3ezVpWBxLzz5xViCo0sKxp7kVVhr1Su0bcMRNNs= +go.temporal.io/sdk v1.40.0/go.mod h1:tauxVfN174F0bdEs27+i0h8UPD7xBb6Py2SPHo7f1C0= go.temporal.io/sdk/contrib/tally v0.2.0 h1:XnTJIQcjOv+WuCJ1u8Ve2nq+s2H4i/fys34MnWDRrOo= go.temporal.io/sdk/contrib/tally v0.2.0/go.mod h1:1kpSuCms/tHeJQDPuuKkaBsMqfHnIIRnCtUYlPNXxuE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= diff --git a/harness/ruby/lib/harness.rb b/harness/ruby/lib/harness.rb index 45131343..cdc9ead3 100644 --- a/harness/ruby/lib/harness.rb +++ b/harness/ruby/lib/harness.rb @@ -7,8 +7,7 @@ module Harness :expect_run_result, :expect_activity_error, :start_callback, - :check_result_callback, - keyword_init: true + :check_result_callback ) @features = {} diff --git a/harness/ruby/runner.rb b/harness/ruby/runner.rb index b824a7da..6f986f79 100644 --- a/harness/ruby/runner.rb +++ b/harness/ruby/runner.rb @@ -84,7 +84,7 @@ def open_summary when 'tcp' TCPSocket.new(uri.host, uri.port) when 'file' - File.open(uri.path, 'w') + File.open(uri.path, 'w') # rubocop:disable Style/FileOpen else raise "Unsupported summary scheme: #{uri.scheme}" end diff --git a/package-lock.json b/package-lock.json index 244b5653..451db3d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,13 @@ "@temporalio/activity": "^1.15.0", "@temporalio/client": "^1.15.0", "@temporalio/common": "^1.15.0", + "@temporalio/plugin": "^1.15.0", "@temporalio/proto": "^1.15.0", "@temporalio/worker": "^1.15.0", "@temporalio/workflow": "^1.15.0", "commander": "^8.3.0", "ms": "^3.0.0-canary.1", + "nexus-rpc": "^0.0.1", "proto3-json-serializer": "^1.1.1", "protobufjs": "7.5.1" }, @@ -747,6 +749,15 @@ "node": ">= 20.0.0" } }, + "node_modules/@temporalio/plugin": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@temporalio/plugin/-/plugin-1.15.0.tgz", + "integrity": "sha512-oXwQn+4HrSSqEg2efTtfenis9RYmWcEz0z56MCYZamYXVPOUSd3kfuDQ9KyJacp+r1zh9C8qh1No07Mm4dIjOA==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + } + }, "node_modules/@temporalio/proto": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.15.0.tgz", @@ -6261,6 +6272,11 @@ "nexus-rpc": "^0.0.1" } }, + "@temporalio/plugin": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@temporalio/plugin/-/plugin-1.15.0.tgz", + "integrity": "sha512-oXwQn+4HrSSqEg2efTtfenis9RYmWcEz0z56MCYZamYXVPOUSd3kfuDQ9KyJacp+r1zh9C8qh1No07Mm4dIjOA==" + }, "@temporalio/proto": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.15.0.tgz", diff --git a/package.json b/package.json index 4af6a55a..57ac770b 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,20 @@ "@temporalio/activity": "^1.15.0", "@temporalio/client": "^1.15.0", "@temporalio/common": "^1.15.0", + "@temporalio/plugin": "^1.15.0", "@temporalio/proto": "^1.15.0", "@temporalio/worker": "^1.15.0", "@temporalio/workflow": "^1.15.0", "commander": "^8.3.0", "ms": "^3.0.0-canary.1", + "nexus-rpc": "^0.0.1", "proto3-json-serializer": "^1.1.1", "protobufjs": "7.5.1" }, "devDependencies": { "@tsconfig/node24": "^24.0.4", - "@types/node": "^24.1.0", "@types/ms": "^2.1.0", + "@types/node": "^24.1.0", "@typescript-eslint/eslint-plugin": "^8.10.0", "@typescript-eslint/parser": "^8.10.0", "eslint": "^9.26.0", diff --git a/sdkbuild/typescript.go b/sdkbuild/typescript.go index a51548d6..4ae2701e 100644 --- a/sdkbuild/typescript.go +++ b/sdkbuild/typescript.go @@ -110,7 +110,7 @@ func BuildTypeScriptProgram(ctx context.Context, options BuildTypeScriptProgramO if err != nil { return nil, fmt.Errorf("cannot get absolute path from version path: %w", err) } - pkgs := []string{"activity", "client", "common", "proto", "worker", "workflow"} + pkgs := []string{"activity", "client", "common", "plugin", "proto", "worker", "workflow"} for _, pkg := range pkgs { pkgPath := "file:" + filepath.Join(localPath, "packages", pkg) packageJSONDepStr += fmt.Sprintf(`"@temporalio/%v": %q,`, pkg, pkgPath) @@ -118,7 +118,7 @@ func BuildTypeScriptProgram(ctx context.Context, options BuildTypeScriptProgramO } } else { version := strings.TrimPrefix(options.Version, "v") - pkgs := []string{"activity", "client", "common", "worker", "workflow"} + pkgs := []string{"activity", "client", "common", "plugin", "worker", "workflow"} for _, pkg := range pkgs { packageJSONDepStr += fmt.Sprintf(` "@temporalio/%v": %q,`, pkg, version) + "\n" }