From 9710f5c799c7abf2baa8e67097623105528cde09 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 19 Feb 2026 11:35:37 -0800 Subject: [PATCH 01/15] Add dotnet and python snippets --- features/snippets/plugins/plugins.cs | 107 ++++++++++++++++++++++++ features/snippets/plugins/plugins.py | 117 +++++++++++++++++++++++++++ features/snippets/plugins/plugins.ts | 85 +++++++++++++++++++ features/snippets/worker/worker.py | 12 +++ 4 files changed, 321 insertions(+) create mode 100644 features/snippets/plugins/plugins.cs create mode 100644 features/snippets/plugins/plugins.py create mode 100644 features/snippets/plugins/plugins.ts create mode 100644 features/snippets/worker/worker.py 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.py b/features/snippets/plugins/plugins.py new file mode 100644 index 00000000..7f3628ff --- /dev/null +++ b/features/snippets/plugins/plugins.py @@ -0,0 +1,117 @@ +import dataclasses +from dataclasses import dataclass + +import nexusrpc +import temporalio.client +import temporalio.worker +from temporalio import activity, workflow +from temporalio.client import Client +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 + + +async def run(): + # @@@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]) + + client = await Client.connect( + "localhost:7233", + plugins=[ + plugin, + ], + ) + await client.execute_workflow( + HelloWorkflow.run, + "Tim", + task_queue="task-queue", + ) + # @@@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.ts b/features/snippets/plugins/plugins.ts new file mode 100644 index 00000000..e2ac8604 --- /dev/null +++ b/features/snippets/plugins/plugins.ts @@ -0,0 +1,85 @@ +import { SimplePlugin } from '@temporalio/plugin'; +import { PayloadCodec, Payload } from '@temporalio/common'; +import { WorkflowClientInterceptor } from '@temporalio/client'; +import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } from '@temporalio/worker'; +import * as nexus from 'nexus-rpc'; + +// @@@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) => ({ + ...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: [ + (_) => ({ + inbound: new MyActivityInboundInterceptor(), + outbound: new MyActivityOutboundInterceptor(), + }), + ], + }, + }); + // @@@SNIPEND +} 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() From e1a1968f4292b73e8e86ab18d0307c39b8fae8f1 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 10:33:51 -0800 Subject: [PATCH 02/15] Python reformatting, add go, java, and ruby --- build.gradle | 2 +- features/go.mod | 5 +- features/go.sum | 12 +- features/snippets/plugins/plugins.go | 120 +++++++++++++++ features/snippets/plugins/plugins.java | 114 ++++++++++++++ features/snippets/plugins/plugins.py | 205 ++++++++++++------------- features/snippets/plugins/plugins.rb | 66 ++++++++ go.mod | 6 +- go.sum | 12 +- 9 files changed, 421 insertions(+), 121 deletions(-) create mode 100644 features/snippets/plugins/plugins.go create mode 100644 features/snippets/plugins/plugins.java create mode 100644 features/snippets/plugins/plugins.rb diff --git a/build.gradle b/build.gradle index b45f1fa6..5ba63753 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.jayway.jsonpath:json-path:2.6.0' implementation 'info.picocli:picocli:4.6.2' - implementation 'io.temporal:temporal-sdk:1.26.1' + implementation 'io.temporal:temporal-sdk:1.33.0' implementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' implementation 'org.reflections:reflections:0.10.2' } 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.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..0a266f36 --- /dev/null +++ b/features/snippets/plugins/plugins.java @@ -0,0 +1,114 @@ +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; +import io.temporal.common.SimplePlugin; +import io.temporal.common.converter.DataConverter; +import io.temporal.common.interceptors.WorkerInterceptor; +import io.temporal.common.interceptors.WorkerInterceptorBase; +import io.temporal.common.interceptors.WorkflowClientInterceptor; +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 +} \ No newline at end of file diff --git a/features/snippets/plugins/plugins.py b/features/snippets/plugins/plugins.py index 7f3628ff..3f4a8fba 100644 --- a/features/snippets/plugins/plugins.py +++ b/features/snippets/plugins/plugins.py @@ -5,7 +5,6 @@ import temporalio.client import temporalio.worker from temporalio import activity, workflow -from temporalio.client import Client from temporalio.contrib.pydantic import pydantic_data_converter from temporalio.converter import DataConverter from temporalio.plugin import SimplePlugin @@ -13,105 +12,105 @@ from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner -async def run(): - # @@@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]) - - client = await Client.connect( - "localhost:7233", - plugins=[ - plugin, - ], - ) - await client.execute_workflow( - HelloWorkflow.run, - "Tim", - task_queue="task-queue", - ) - # @@@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 +# @@@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/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= From 3f31b386a5e5c85dc9058157e486c4e997b4832f Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 11:56:12 -0800 Subject: [PATCH 03/15] Fix TypeScript and Java plugin snippets compilation issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update TypeScript SDK to 1.15.0 and add @temporalio/plugin package - Fix Java formatting issues to pass spotless checks - Remove unused imports from Java file 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- features/snippets/plugins/plugins.java | 169 +++++++++++++------------ package.json | 13 +- 2 files changed, 94 insertions(+), 88 deletions(-) diff --git a/features/snippets/plugins/plugins.java b/features/snippets/plugins/plugins.java index 0a266f36..1e70e936 100644 --- a/features/snippets/plugins/plugins.java +++ b/features/snippets/plugins/plugins.java @@ -1,114 +1,119 @@ import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; import io.temporal.common.SimplePlugin; -import io.temporal.common.converter.DataConverter; -import io.temporal.common.interceptors.WorkerInterceptor; import io.temporal.common.interceptors.WorkerInterceptorBase; -import io.temporal.common.interceptors.WorkflowClientInterceptor; 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(); - } + // @@@SNIPSTART java-plugin-activity + @ActivityInterface + public interface SomeActivity { + @ActivityMethod + void someActivity(); + } - public class SomeActivityImpl implements SomeActivity { - @Override - public void someActivity() { - // Activity implementation - } + public class SomeActivityImpl implements SomeActivity { + @Override + public void someActivity() { + // Activity implementation } + } + + SimplePlugin activityPlugin = + SimplePlugin.newBuilder("PluginName") + .registerActivitiesImplementations(new SomeActivityImpl()) + .build(); + // @@@SNIPEND - SimplePlugin activityPlugin = SimplePlugin.newBuilder("PluginName") - .registerActivitiesImplementations(new SomeActivityImpl()) - .build(); - // @@@SNIPEND + // @@@SNIPSTART java-plugin-workflow + @WorkflowInterface + public interface HelloWorkflow { + @WorkflowMethod + String run(String name); + } - // @@@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 + "!"; } + } - 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; - 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 Weather(String city, String temperatureRange, String conditions) { + this.city = city; + this.temperatureRange = temperatureRange; + this.conditions = conditions; } - 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... + // Getters... + } + + public static class WeatherInput { + private final String city; + + public WeatherInput(String city) { + this.city = city; } - public static class WeatherInput { - private final String city; - - public WeatherInput(String city) { - this.city = city; - } - - public String getCity() { return city; } + public String getCity() { + return city; } + } - SimplePlugin nexusPlugin = SimplePlugin.newBuilder("PluginName") - .registerNexusServiceImplementation(new WeatherService()) - .build(); - // @@@SNIPEND + SimplePlugin nexusPlugin = + SimplePlugin.newBuilder("PluginName") + .registerNexusServiceImplementation(new WeatherService()) + .build(); + // @@@SNIPEND - // @@@SNIPSTART java-plugin-converter - SimplePlugin converterPlugin = SimplePlugin.newBuilder("PluginName") - .customizeDataConverter(existingConverter -> { + // @@@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 + }) + .build(); + // @@@SNIPEND - // @@@SNIPSTART java-plugin-interceptors - public class SomeWorkerInterceptor extends WorkerInterceptorBase { - // Your worker interceptor implementation - } + // @@@SNIPSTART java-plugin-interceptors + public class SomeWorkerInterceptor extends WorkerInterceptorBase { + // Your worker interceptor implementation + } - public class SomeClientInterceptor extends WorkflowClientInterceptorBase { - // Your client interceptor implementation - } + public class SomeClientInterceptor extends WorkflowClientInterceptorBase { + // Your client interceptor implementation + } - SimplePlugin interceptorPlugin = SimplePlugin.newBuilder("PluginName") - .addWorkerInterceptors(new SomeWorkerInterceptor()) - .addClientInterceptors(new SomeClientInterceptor()) - .build(); - // @@@SNIPEND -} \ No newline at end of file + SimplePlugin interceptorPlugin = + SimplePlugin.newBuilder("PluginName") + .addWorkerInterceptors(new SomeWorkerInterceptor()) + .addClientInterceptors(new SomeClientInterceptor()) + .build(); + // @@@SNIPEND +} diff --git a/package.json b/package.json index 25b3cc1d..1dc88239 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ "dependencies": { "@grpc/grpc-js": "^1.12.4", "@protobuf-ts/protoc": "^2.8.1", - "@temporalio/activity": "^1.11.8", - "@temporalio/client": "^1.11.8", - "@temporalio/common": "^1.11.8", - "@temporalio/proto": "^1.11.8", - "@temporalio/worker": "^1.11.8", - "@temporalio/workflow": "^1.11.8", + "@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", "protobufjs": "7.5.1", "commander": "^8.3.0", "ms": "^3.0.0-canary.1", From 2c74fb47b62cb177e52f043db0ea10a60bf53992 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 11:59:53 -0800 Subject: [PATCH 04/15] Update lock --- package-lock.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/package-lock.json b/package-lock.json index 244b5653..04383e38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@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", @@ -747,6 +748,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 +6271,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", From 533179f227a113cdeba926e19a24353ae17e426c Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 12:07:38 -0800 Subject: [PATCH 05/15] TS linting fixes --- features/snippets/plugins/plugins.ts | 33 ++++++++++++++-------------- package-lock.json | 1 + package.json | 3 ++- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/features/snippets/plugins/plugins.ts b/features/snippets/plugins/plugins.ts index e2ac8604..10ce8d21 100644 --- a/features/snippets/plugins/plugins.ts +++ b/features/snippets/plugins/plugins.ts @@ -1,19 +1,20 @@ +import * as nexus from 'nexus-rpc'; import { SimplePlugin } from '@temporalio/plugin'; import { PayloadCodec, Payload } from '@temporalio/common'; import { WorkflowClientInterceptor } from '@temporalio/client'; import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } from '@temporalio/worker'; -import * as nexus from 'nexus-rpc'; - -// @@@SNIPSTART typescript-plugins-activity -const activity = async () => 'activity'; -const plugin = new SimplePlugin({ - name: 'plugin-name', - activities: { - pluginActivity: activity, - }, -}); -// @@@SNIPEND +{ + // @@@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( @@ -26,7 +27,7 @@ const plugin = new SimplePlugin({ }, } ); - const plugin = new SimplePlugin({ + const _plugin = new SimplePlugin({ name: 'plugin-name', nexusServices: [testServiceHandler], }); @@ -36,14 +37,14 @@ const plugin = new SimplePlugin({ { // @@@SNIPSTART typescript-plugins-converter const codec: PayloadCodec = { - encode(payloads: Payload[]): Promise { + encode(_payloads: Payload[]): Promise { throw new Error(); }, - decode(payloads: Payload[]): Promise { + decode(_payloads: Payload[]): Promise { throw new Error(); }, }; - const plugin = new SimplePlugin({ + const _plugin = new SimplePlugin({ name: 'plugin-name', dataConverter: (converter) => ({ ...converter, @@ -63,7 +64,7 @@ const plugin = new SimplePlugin({ const workflowInterceptorsPath = ''; - const plugin = new SimplePlugin({ + const _plugin = new SimplePlugin({ name: 'plugin-name', clientInterceptors: { workflow: [new MyWorkflowClientInterceptor()], diff --git a/package-lock.json b/package-lock.json index 04383e38..451db3d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@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" }, diff --git a/package.json b/package.json index f3df8f9d..57ac770b 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ "@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", From 06e5a6fa008fbe3cd7cc13c7d217c5225f7d4e48 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 12:10:13 -0800 Subject: [PATCH 06/15] Format fix --- features/snippets/plugins/plugins.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/snippets/plugins/plugins.ts b/features/snippets/plugins/plugins.ts index 10ce8d21..52cc52b4 100644 --- a/features/snippets/plugins/plugins.ts +++ b/features/snippets/plugins/plugins.ts @@ -25,7 +25,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro async testSyncOp(_, input) { return input; }, - } + }, ); const _plugin = new SimplePlugin({ name: 'plugin-name', From 358aedb5f59209938213c66291041a8939a998c5 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 12:21:34 -0800 Subject: [PATCH 07/15] Include plugin package --- sdkbuild/typescript.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdkbuild/typescript.go b/sdkbuild/typescript.go index a51548d6..8d19f792 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) From 98fc79e8243fe2b52e94091a900b034b5a9816f2 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 12:41:31 -0800 Subject: [PATCH 08/15] Add plugin package --- features/snippets/plugins/plugins.ts | 7 ++++--- sdkbuild/typescript.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/features/snippets/plugins/plugins.ts b/features/snippets/plugins/plugins.ts index 52cc52b4..a619c34d 100644 --- a/features/snippets/plugins/plugins.ts +++ b/features/snippets/plugins/plugins.ts @@ -1,6 +1,7 @@ import * as nexus from 'nexus-rpc'; +import { Context } from '@temporalio/activity'; import { SimplePlugin } from '@temporalio/plugin'; -import { PayloadCodec, Payload } from '@temporalio/common'; +import { DataConverter, PayloadCodec, Payload } from '@temporalio/common'; import { WorkflowClientInterceptor } from '@temporalio/client'; import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } from '@temporalio/worker'; @@ -46,7 +47,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro }; const _plugin = new SimplePlugin({ name: 'plugin-name', - dataConverter: (converter) => ({ + dataConverter: (converter: DataConverter) => ({ ...converter, payloadCodecs: [...(converter?.payloadCodecs ?? []), codec], }), @@ -75,7 +76,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro }, workflowModules: [workflowInterceptorsPath], activity: [ - (_) => ({ + (_: Context) => ({ inbound: new MyActivityInboundInterceptor(), outbound: new MyActivityOutboundInterceptor(), }), diff --git a/sdkbuild/typescript.go b/sdkbuild/typescript.go index 8d19f792..4ae2701e 100644 --- a/sdkbuild/typescript.go +++ b/sdkbuild/typescript.go @@ -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" } From fdfbd226a666cd72fb1566d43a3d72bfdd7f9314 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 12:44:09 -0800 Subject: [PATCH 09/15] Fix type --- features/snippets/plugins/plugins.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/snippets/plugins/plugins.ts b/features/snippets/plugins/plugins.ts index a619c34d..4c502cfd 100644 --- a/features/snippets/plugins/plugins.ts +++ b/features/snippets/plugins/plugins.ts @@ -26,7 +26,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro async testSyncOp(_, input) { return input; }, - }, + } ); const _plugin = new SimplePlugin({ name: 'plugin-name', @@ -47,7 +47,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro }; const _plugin = new SimplePlugin({ name: 'plugin-name', - dataConverter: (converter: DataConverter) => ({ + dataConverter: (converter: DataConverter | undefined) => ({ ...converter, payloadCodecs: [...(converter?.payloadCodecs ?? []), codec], }), From 822fc658410dbbefa0f0d3b08f2c09b0a8972f03 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 25 Feb 2026 12:45:59 -0800 Subject: [PATCH 10/15] Format --- features/snippets/plugins/plugins.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/snippets/plugins/plugins.ts b/features/snippets/plugins/plugins.ts index 4c502cfd..5809006e 100644 --- a/features/snippets/plugins/plugins.ts +++ b/features/snippets/plugins/plugins.ts @@ -26,7 +26,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro async testSyncOp(_, input) { return input; }, - } + }, ); const _plugin = new SimplePlugin({ name: 'plugin-name', From 0eeb76357de51e890dbd3366c46a26068b13af82 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 26 Feb 2026 08:29:55 -0800 Subject: [PATCH 11/15] Remove _ from plugin var --- features/snippets/plugins/plugins.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/features/snippets/plugins/plugins.ts b/features/snippets/plugins/plugins.ts index 5809006e..5212be26 100644 --- a/features/snippets/plugins/plugins.ts +++ b/features/snippets/plugins/plugins.ts @@ -4,11 +4,12 @@ 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({ + const plugin = new SimplePlugin({ name: 'plugin-name', activities: { pluginActivity: activity, @@ -28,7 +29,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro }, }, ); - const _plugin = new SimplePlugin({ + const plugin = new SimplePlugin({ name: 'plugin-name', nexusServices: [testServiceHandler], }); @@ -45,7 +46,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro throw new Error(); }, }; - const _plugin = new SimplePlugin({ + const plugin = new SimplePlugin({ name: 'plugin-name', dataConverter: (converter: DataConverter | undefined) => ({ ...converter, @@ -65,7 +66,7 @@ import { ActivityInboundCallsInterceptor, ActivityOutboundCallsInterceptor } fro const workflowInterceptorsPath = ''; - const _plugin = new SimplePlugin({ + const plugin = new SimplePlugin({ name: 'plugin-name', clientInterceptors: { workflow: [new MyWorkflowClientInterceptor()], From ecb790e6e93cee3040735caf9fa029f0352d1414 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 26 Feb 2026 08:58:09 -0800 Subject: [PATCH 12/15] Add worker cache snippets --- features/snippets/worker/worker.cs | 21 +++++++++++++++++++++ features/snippets/worker/worker.go | 23 +++++++++++++++++++++++ features/snippets/worker/worker.java | 21 +++++++++++++++++++++ features/snippets/worker/worker.rb | 21 +++++++++++++++++++++ features/snippets/worker/worker.ts | 17 +++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 features/snippets/worker/worker.cs create mode 100644 features/snippets/worker/worker.go create mode 100644 features/snippets/worker/worker.java create mode 100644 features/snippets/worker/worker.rb create mode 100644 features/snippets/worker/worker.ts diff --git a/features/snippets/worker/worker.cs b/features/snippets/worker/worker.cs new file mode 100644 index 00000000..9fac598e --- /dev/null +++ b/features/snippets/worker/worker.cs @@ -0,0 +1,21 @@ +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 + + await worker.ExecuteAsync(); + } +} \ No newline at end of file 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..038a5627 --- /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.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..e7fa4bb1 --- /dev/null +++ b/features/snippets/worker/worker.ts @@ -0,0 +1,17 @@ +import { NativeConnection, Worker } from '@temporalio/worker'; + +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 + + await worker.run(); +} \ No newline at end of file From 1e6ea2e9ca5164e629bc24cef1b5ba50198ec77b Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 26 Feb 2026 09:04:04 -0800 Subject: [PATCH 13/15] Fix some lint issues --- features/snippets/worker/worker.cs | 6 ++---- features/snippets/worker/worker.java | 6 +++--- features/snippets/worker/worker.ts | 9 ++++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/features/snippets/worker/worker.cs b/features/snippets/worker/worker.cs index 9fac598e..5d0f77a4 100644 --- a/features/snippets/worker/worker.cs +++ b/features/snippets/worker/worker.cs @@ -6,7 +6,7 @@ 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, @@ -15,7 +15,5 @@ public static async Task Run() MaxCachedWorkflows = 0 }); // @@@SNIPEND - - await worker.ExecuteAsync(); } -} \ No newline at end of file +} diff --git a/features/snippets/worker/worker.java b/features/snippets/worker/worker.java index 038a5627..3a3ee8c4 100644 --- a/features/snippets/worker/worker.java +++ b/features/snippets/worker/worker.java @@ -10,9 +10,9 @@ public static void main(String[] args) { WorkflowClient client = WorkflowClient.newInstance(service); // @@@SNIPSTART java-worker-max-cached-workflows - WorkerFactory factory = WorkerFactory.newInstance( - client, - WorkerFactoryOptions.newBuilder().setWorkflowCacheSize(0).build()); + WorkerFactory factory = + WorkerFactory.newInstance( + client, WorkerFactoryOptions.newBuilder().setWorkflowCacheSize(0).build()); Worker worker = factory.newWorker("task-queue"); // @@@SNIPEND diff --git a/features/snippets/worker/worker.ts b/features/snippets/worker/worker.ts index e7fa4bb1..a725b4fa 100644 --- a/features/snippets/worker/worker.ts +++ b/features/snippets/worker/worker.ts @@ -1,10 +1,11 @@ import { NativeConnection, Worker } from '@temporalio/worker'; +/* eslint-disable @typescript-eslint/no-unused-vars */ -async function run() { +async function _run() { const connection = await NativeConnection.connect({ address: 'localhost:7233', }); - + // @@@SNIPSTART typescript-worker-max-cached-workflows const worker = await Worker.create({ connection, @@ -12,6 +13,4 @@ async function run() { maxCachedWorkflows: 0, }); // @@@SNIPEND - - await worker.run(); -} \ No newline at end of file +} From 5673122e49172e25ec293673db90eb80c373c6ff Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 26 Feb 2026 09:21:57 -0800 Subject: [PATCH 14/15] Rubocop changes --- .rubocop.yml | 4 ++++ harness/ruby/lib/harness.rb | 1 - harness/ruby/runner.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) 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/harness/ruby/lib/harness.rb b/harness/ruby/lib/harness.rb index 45131343..375db5c6 100644 --- a/harness/ruby/lib/harness.rb +++ b/harness/ruby/lib/harness.rb @@ -8,7 +8,6 @@ module Harness :expect_activity_error, :start_callback, :check_result_callback, - keyword_init: true ) @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 From 9012d47b1bba0cce9a0ec82f14e18ff43c3be277 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 26 Feb 2026 09:23:20 -0800 Subject: [PATCH 15/15] Rubocop changes --- harness/ruby/lib/harness.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/ruby/lib/harness.rb b/harness/ruby/lib/harness.rb index 375db5c6..cdc9ead3 100644 --- a/harness/ruby/lib/harness.rb +++ b/harness/ruby/lib/harness.rb @@ -7,7 +7,7 @@ module Harness :expect_run_result, :expect_activity_error, :start_callback, - :check_result_callback, + :check_result_callback ) @features = {}