=============== LIBRARY RULES =============== From library maintainers: - Declarative API (i.e. `activity("name") { ... }` ) is not yet implemented as the compiler plugin is not finished. Only use Imperative API. ### Starting Workflows (Client) Source: https://github.com/snipesy/temporal-kt/blob/main/docs/JAVA_MIGRATE.md Examples of how to start a workflow from a client application in Java and Kotlin. ```APIDOC ## Starting Workflows (Client) ### Description Initiates a new workflow execution from a client. ### Method `client.newWorkflowStub` (Java), `client.startWorkflow` (Kotlin) ### Endpoint N/A (Client-side initiation) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **taskQueue** (string) - Required - The task queue the workflow will run on. - **arg** (string) - Required - The initial argument for the workflow. ### Request Example ```kotlin val handle = client.startWorkflow( workflowType = "GreetingWorkflow", taskQueue = "my-task-queue", arg = "World" ) ``` ### Response #### Success Response (200) - **WorkflowHandle** - An object to interact with the started workflow. #### Response Example ```json { "example": "WorkflowHandle object" } ``` ``` -------------------------------- ### Basic Ktor Setup with Temporal Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0013-ktor-subroutine.md Initializes a Temporal application and installs Ktor, setting the port and defining a basic health check and an order creation endpoint alongside a workflow and activity. ```kotlin fun main() = temporalApplication { install(Ktor) { port = 8080 } taskQueue("main") { // HTTP routes alongside workflows/activities get("/health") { call.respondText("OK") } post("/orders") { val request = call.receive() val handle = temporalClient.startWorkflow(request) call.respond(OrderResponse(workflowId = handle.workflowId)) } workflow() activity(PaymentActivity()) } }.start() ``` -------------------------------- ### Quick Start: Install OpenTelemetry Plugin Source: https://github.com/snipesy/temporal-kt/blob/main/plugins/opentelemetry/README.md Install the OpenTelemetryPlugin to add observability to your Temporal application. Configure the tracer name to identify your service. ```kotlin fun main() { val app = embeddedTemporal( module = { // Install the OpenTelemetry plugin install(OpenTelemetryPlugin) { tracerName = "my-service" } taskQueue("my-queue") { workflow() activity(PaymentActivity()) } } ) app.start(wait = true) } ``` -------------------------------- ### Starting Workers Source: https://github.com/snipesy/temporal-kt/blob/main/docs/JAVA_MIGRATE.md Code examples for starting Temporal workers in Java and Kotlin. ```APIDOC ## Starting Workers ### Description Initializes and starts Temporal workers to process workflows and activities. ### Method Worker initialization and start. ### Endpoint N/A (Worker setup) ### Parameters None ### Request Example ```kotlin // Kotlin example using embeddedTemporal embeddedTemporal(module = { taskQueue("my-task-queue") { workflow() activity(GreetingActivities()) } }).start(wait = true) ``` ### Response Worker starts processing tasks. ``` -------------------------------- ### Invoke a Workflow from a Client Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Connect to the Temporal server and start a workflow from a client application. This example shows how to start the OrdersWorkflow and retrieve its result. ```kotlin import kotlinx.coroutines.runBlocking fun main() = runBlocking { val client = TemporalClient.connect { target = "localhost:7233" namespace = "default" } // Start workflow - type parameters no longer needed on startWorkflow val workflowHandle = client.startWorkflow( OrdersWorkflow::class, // or "OrdersWorkflow" as a string "hello-world-queue", arg = "12345" // orderId argument ) // Get result with type parameter val result = workflowHandle.result() println("Workflow result: $result") } ``` -------------------------------- ### Installation and Configuration Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0012-payload-serializer.md How to install the SerializationPlugin and configure built-in serializers. ```APIDOC ## Installation ```kotlin fun TemporalApplication.module() { install(SerializationPlugin) { json() // kotlinx.serialization JSON (default) } } ``` ## Built-in Serializers ### JSON (Default) ```kotlin install(SerializationPlugin) { json { prettyPrint = false encodeDefaults = true ignoreUnknownKeys = true } } ``` ``` -------------------------------- ### Install SDKs and Environment Source: https://github.com/snipesy/temporal-kt/blob/main/docs/DEV_SETUP.md Installs necessary development tools like Java, Gradle, Rust, and Protobuf. Ensure your environment is set up correctly before proceeding. ```bash sdk env install ``` -------------------------------- ### Workflow Cancellation Example (Kotlin) Source: https://github.com/snipesy/temporal-kt/blob/main/docs/ACTIVITIES_IMPERATIVE.md Demonstrates how to start an activity with cancellation options and handle potential exceptions like cancellation, timeouts, and failures on the workflow side. ```kotlin @Workflow("CancellableWorkflow") class CancellableWorkflow { @WorkflowRun suspend fun run(): String { val handle = workflow().startActivity( activityType = "longRunningTask", scheduleToCloseTimeout = 10.minutes, heartbeatTimeout = 30.seconds, cancellationType = ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, ) // Wait a bit then cancel workflow().sleep(5.seconds) handle.cancel() return try { handle.result() } catch (e: com.surrealdev.temporal.common.exceptions.WorkflowActivityCancelledException) { "Activity was cancelled" } catch (e: WorkflowActivityTimeoutException) { "Activity timed out: ${e.timeoutType}" } catch (e: WorkflowActivityFailureException) { "Activity failed: ${e.applicationFailure?.message}" } } } ``` -------------------------------- ### CodecPlugin Installation Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0011-payload-codec.md Shows how to install and configure Payload Codecs within a Temporal application. ```APIDOC ## Installation ```kotlin fun TemporalApplication.module() { install(CodecPlugin) { custom(EncryptionCodec( keyId = "production-key", keyProvider = { kmsClient.getKey("production-key") } )) } // Or with chaining install(CodecPlugin) { chained { compression(threshold = 512) codec(EncryptionCodec(keyId = "production-key") { kmsClient.getKey(it) }) } } taskQueue("secure-queue") { workflow() } } ``` ``` -------------------------------- ### Timeout Options Example Workflow (Kotlin) Source: https://github.com/snipesy/temporal-kt/blob/main/docs/ACTIVITIES_IMPERATIVE.md Illustrates the usage of different timeout options when starting an activity, such as startToCloseTimeout, scheduleToCloseTimeout, scheduleToStartTimeout, and heartbeatTimeout. ```kotlin @Workflow("TimeoutExampleWorkflow") class TimeoutExampleWorkflow { @WorkflowRun suspend fun run(): String { return workflow().startActivity( activityType = "myActivity", // Max time for a single execution attempt // If exceeded, activity is retried (if retries remain) startToCloseTimeout = 30.seconds, // Max total time from scheduling to completion // Includes all retry attempts scheduleToCloseTimeout = 5.minutes, // Max time waiting for a worker to pick up the task // Non-retryable if exceeded scheduleToStartTimeout = 1.minutes, // Max time between heartbeats // Required for cancellation detection heartbeatTimeout = 10.seconds, ).result() } } ``` -------------------------------- ### Manual Plugin Creation in Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md Use this for plugins requiring fine-grained control over installation and configuration. It demonstrates how to define a plugin with custom installation logic. ```kotlin class AuthPlugin(val config: AuthConfig) { companion object : ApplicationPlugin { override val key = AttributeKey("Auth") override fun install( pipeline: TemporalApplication, configure: AuthConfig.() -> Unit, ): AuthPlugin { val config = AuthConfig().apply(configure) val plugin = AuthPlugin(config) val builder = createPluginBuilder(pipeline, config, key) builder.workflow { onExecute { input, proceed -> if (config.requireAuth) { validateHeaders(input.headers) } proceed(input) } } builder.hooks.forEach { it.install(pipeline.hookRegistry) } installInterceptors(builder, pipeline) return plugin } } } ``` -------------------------------- ### Install and Configure a Plugin in TemporalApplication Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md Installs the 'MyPlugin' into a TemporalApplication instance and configures its properties. Ensure the Temporal service is accessible at the specified target. ```kotlin val app = TemporalApplication { connection { target = "localhost:7233" } } app.install(MyPlugin) { enabled = true } ``` -------------------------------- ### Start Temporal-Kt Application Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CONFIGURATION.md Initializes and starts an embedded Temporal application with a specified connection and task queue configuration. ```kotlin fun main() { embeddedTemporal( configure = { connection { target = "localhost:7233" namespace = "default" } }, module = { taskQueue("my-queue") { workflow() activity(MyActivity()) } } ).start(wait = true) } ``` -------------------------------- ### SerializationPlugin Installation Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0012-payload-serializer.md Basic installation of the SerializationPlugin within a TemporalApplication module. ```kotlin fun TemporalApplication.module() { install(SerializationPlugin) { json() // kotlinx.serialization JSON (default) } } ``` -------------------------------- ### Install a Custom Codec Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CODECS_AND_SERIALIZATION.md Register the custom codec using the CodecPlugin installation block. ```kotlin install(CodecPlugin) { custom(EncryptionCodec(keyProvider)) } ``` -------------------------------- ### Install Application-Level Plugins Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CONFIGURATION.md Install plugins like SerializationPlugin and CodecPlugin at the application level to apply them globally. Configure plugin settings as needed. ```kotlin embeddedTemporal(module = { // Application-wide serialization install(SerializationPlugin) { json { ignoreUnknownKeys = true } } // Application-wide compression install(CodecPlugin) { compression(threshold = 1024) } taskQueue("my-queue") { workflow() } }) ``` -------------------------------- ### Production Deployment Configuration Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0013-ktor-subroutine.md Example YAML configuration for deploying Ktor alongside Temporal workers, enabling both features and specifying the Ktor port. This setup allows for a single deployment unit. ```yaml # application.yaml temporal: ktor: enabled: true port: 8080 workers: enabled: true ``` -------------------------------- ### Create a Custom Temporal-Kt Plugin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md Define a new plugin named 'MyPlugin' with custom configuration and register interceptors for application setup, workflow execution, and activity execution. This example demonstrates how to hook into different stages of the Temporal lifecycle. ```kotlin val MyPlugin = createApplicationPlugin( name = "MyPlugin", createConfiguration = { MyPluginConfig() }, ) { config -> application { onSetup { ctx -> println("Application started") } } workflow { // Interceptor: wraps each workflow execution onExecute { input, proceed -> println("Executing workflow: ${input.workflowType}") proceed(input) } // Observer hook: fires once per activation (which may contain many operations) onTaskCompleted { ctx -> println("Activation took ${ctx.duration}") } } activity { // Interceptor: wraps each activity execution onExecute { input, proceed -> println("Executing activity: ${input.activityType}") proceed(input) } } Unit } data class MyPluginConfig( var enabled: Boolean = true, ) ``` -------------------------------- ### Start Workflow with Search Attributes Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Demonstrates how to pass search attributes during workflow initiation using the client. ```kotlin import com.surrealdev.temporal.common.searchAttributes import java.time.Instant fun main() = runBlocking { val client = TemporalClient.connect { target = "localhost:7233" namespace = "default" } val handle = client.startWorkflow( workflowType = "OrderWorkflow", taskQueue = "orders", options = WorkflowStartOptions( searchAttributes = searchAttributes { CUSTOMER_ID to "cust-123" ORDER_COUNT to 42L IS_PREMIUM to true CREATED_AT to Instant.now() TOTAL_AMOUNT to 199.99 TAGS to listOf("urgent", "vip") } ) ) val result = handle.result() } ``` -------------------------------- ### Install HealthCheckPlugin Source: https://github.com/snipesy/temporal-kt/blob/main/plugins/health/README.md Install the HealthCheckPlugin and configure its ports and paths for readiness, liveness, and health checks. Use port 0 for an OS-assigned port. ```kotlin app.install(HealthCheckPlugin) { port = 8080 // default; use 0 for OS-assigned port readyPath = "/readyz" livePath = "/livez" healthPath = "/healthz" } ``` -------------------------------- ### Basic Routing Setup in Temporal-Kt Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0009-routing.md Demonstrates how to define routes to prepend prefixes to workflow and activity type names, creating a hierarchical structure. ```kotlin fun TemporalApplication.module() { taskQueue("main") { route("orders") { workflow() // → "orders/CreateOrder" workflow() // → "orders/CancelOrder" activity(PaymentActivity()) // → "orders/Payment" route("fulfillment") { workflow() // → "orders/fulfillment/ShipOrder" activity(InventoryActivity()) // → "orders/fulfillment/Inventory" } } route("users") { workflow() // → "users/Onboarding" activity(EmailActivity()) // → "users/Email" } } } ``` -------------------------------- ### Complete Tracing Plugin Example Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md A comprehensive example of a tracing plugin. It uses interceptors for per-operation tracing and hooks for activation-level metrics. Requires `TracingConfig`. ```kotlin val TracingPlugin = createApplicationPlugin( name = "Tracing", createConfiguration = { TracingConfig() }, ) { application { onSetup { ctx -> println("[tracing] Application starting") } onWorkerStarted { ctx -> println("[tracing] Worker started: ${ctx.taskQueue}") } } workflow { // Interceptors: per-operation tracing onExecute { input, proceed -> println("[tracing] Workflow ${input.workflowType} started") try { val result = proceed(input) println("[tracing] Workflow ${input.workflowType} completed") result } catch (e: Exception) { println("[tracing] Workflow ${input.workflowType} failed: ${e.message}") throw e } } onScheduleActivity { input, proceed -> println("[tracing] Scheduling activity: ${input.activityType}") proceed(input) } // Hook: activation-level metric (no interceptor equivalent) onTaskCompleted { ctx -> println("[tracing] Workflow activation took ${ctx.duration}") } } activity { // Interceptor: per-activity tracing onExecute { input, proceed -> println("[tracing] Activity ${input.activityType} started") val result = proceed(input) println("[tracing] Activity ${input.activityType} completed") result } } Unit } data class TracingConfig( var verbose: Boolean = false, ) ``` ```kotlin fun main() { embeddedTemporal(module = { install(TracingPlugin) { verbose = true } taskQueue("my-queue") { workflow() activity(MyActivities()) } }).start(wait = true) } ``` -------------------------------- ### Convention Plugin (build-logic) Installation Source: https://github.com/snipesy/temporal-kt/blob/main/plugins/jib/README.md Install the Temporal Jib plugin in a convention plugin setup. Add the plugin to the root buildscript dependencies and apply it within the convention plugin's configuration block. ```kotlin // root build.gradle.kts buildscript { dependencies { classpath("com.surrealdev.temporal:jib-plugin:VERSION") } } ``` ```kotlin // build-logic/convention/src/main/kotlin/my-jib-convention.gradle.kts plugins { id("com.google.cloud.tools.jib") id("com.surrealdev.temporal") } jib { pluginExtensions { pluginExtension { implementation = "com.surrealdev.temporal.gradle.jib.TemporalJibExtension" } } } ``` -------------------------------- ### Start Temporal Dev Server Source: https://github.com/snipesy/temporal-kt/blob/main/docs/GETTING_STARTED.md Command to launch the Temporal development server. This is a prerequisite for running Temporal applications locally. ```bash temporal server start-dev ``` -------------------------------- ### Start Parallel Local Activities Source: https://github.com/snipesy/temporal-kt/blob/main/docs/ACTIVITIES_IMPERATIVE.md Execute multiple local activities concurrently by starting them and collecting their handles. Use `handles.map { it.result() }` to await all results. ```kotlin @Workflow("ParallelLocalWorkflow") class ParallelLocalWorkflow { @WorkflowRun suspend fun run(): List { val ctx = workflow() // Start multiple local activities val handles = listOf("task1", "task2", "task3").map { taskId -> ctx.startLocalActivity( activityType = "processTask", arg = taskId, startToCloseTimeout = 10.seconds, ) } // Wait for all to complete return handles.map { it.result() } } } ``` -------------------------------- ### Creating a Scoped Plugin in Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md Define a plugin using `createScopedPlugin` that can be installed at both application and task-queue levels. This example shows a metrics plugin. ```kotlin val MetricsPlugin = createScopedPlugin( name = "Metrics", createConfiguration = { MetricsConfig() }, ) { workflow { onExecute { input, proceed -> val result = proceed(input) recordMetric("workflow.completed", input.workflowType) result } onTaskCompleted { ctx -> recordMetric("workflow.activation.duration", ctx.duration) } } Unit } ``` -------------------------------- ### Hello World Temporal Application Source: https://github.com/snipesy/temporal-kt/blob/main/docs/GETTING_STARTED.md A minimal Temporal Kotlin application demonstrating workflow and activity definitions. Ensure the Temporal server is running and the CLI is available for starting workflows. ```kotlin import com.surrealdev.temporal.annotation.Activity import com.surrealdev.temporal.annotation.Workflow import com.surrealdev.temporal.annotation.WorkflowRun import com.surrealdev.temporal.application.embeddedTemporal import com.surrealdev.temporal.application.taskQueue import com.surrealdev.temporal.workflow.startActivity import com.surrealdev.temporal.workflow.workflow import kotlin.time.Duration.Companion.seconds fun main() { val app = embeddedTemporal( module = { taskQueue("hello-world-queue") { workflow() activity(GreetingActivity()) } } ) println("Starting Temporal application on task queue: hello-world-queue") println("Start a workflow with: temporal workflow start --task-queue hello-world-queue --type GreetingWorkflow --input '"World"'") app.start(wait = true) } @Workflow("GreetingWorkflow") class GreetingWorkflow { @WorkflowRun suspend fun run(name: String): String { val greeting = workflow() .startActivity( GreetingActivity::formatGreeting, arg = name, scheduleToCloseTimeout = 10.seconds ).result() return greeting } } class GreetingActivity { @Activity("formatGreeting") fun formatGreeting(name: String): String { return "Hello, $name!" } } ``` -------------------------------- ### Custom Serializer Implementation (MessagePack Example) Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0012-payload-serializer.md Example of implementing a custom PayloadSerializer for MessagePack format. ```APIDOC ## Custom Serializer Implement `PayloadSerializer` for custom formats. Use `TemporalPayload.create()` factories and `TemporalByteString` to avoid touching protobuf types: ```kotlin class MessagePackSerializer : PayloadSerializer { private val msgpack = MessagePack.newDefaultPacker() private val MSGPACK_META = mapOf( TemporalPayload.METADATA_ENCODING to TemporalByteString.fromUtf8("binary/msgpack") ) private val NULL_META = mapOf( TemporalPayload.METADATA_ENCODING to TemporalByteString.fromUtf8(TemporalPayload.ENCODING_NULL) ) override fun serialize(typeInfo: KType, value: Any?): TemporalPayload { if (value == null) return TemporalPayload.create(NULL_META) return TemporalPayload.create(MSGPACK_META) { stream -> msgpack.pack(value, stream) } } override fun deserialize(typeInfo: KType, payload: TemporalPayload): Any? { if (payload.encoding == TemporalPayload.ENCODING_NULL) return null return msgpack.unpack(payload.dataInputStream(), typeInfo.javaType) } } // Usage install(SerializationPlugin) { custom(MessagePackSerializer()) } ``` ``` -------------------------------- ### Install Dependency Source: https://github.com/snipesy/temporal-kt/blob/main/plugins/di/README.md Add the dependency to your build.gradle.kts file. ```kotlin dependencies { implementation("com.surrealdev.temporal:di:$version") } ``` -------------------------------- ### Start Workflows via Client Source: https://github.com/snipesy/temporal-kt/blob/main/docs/JAVA_MIGRATE.md Initialize a workflow stub or client handle to trigger workflow execution on a specific task queue. ```java // Java WorkflowClient client = WorkflowClient.newInstance(service); GreetingWorkflow workflow = client.newWorkflowStub( GreetingWorkflow.class, WorkflowOptions.newBuilder() .setTaskQueue("my-task-queue") .build() ); String result = workflow.getGreeting("World"); ``` ```kotlin // Kotlin val client = TemporalClient.connect { target = "localhost:7233" namespace = "default" } val handle = client.startWorkflow( workflowType = "GreetingWorkflow", taskQueue = "my-task-queue", arg = "World", ) val result = handle.result() ``` -------------------------------- ### Start a Simple Local Activity Source: https://github.com/snipesy/temporal-kt/blob/main/docs/ACTIVITIES_IMPERATIVE.md Initiate a local activity from a workflow using `startLocalActivity`. This is suitable for short, in-process operations. Ensure `startToCloseTimeout` is set appropriately. ```kotlin @Workflow("LocalActivityWorkflow") class LocalActivityWorkflow { @WorkflowRun suspend fun run(): String { // Simple local activity val result = workflow().startLocalActivity( activityType = "quickValidation", startToCloseTimeout = 10.seconds, ).result() return result } } ``` -------------------------------- ### Plugin Management Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CONFIGURATION.md Plugins can be installed at the application level or overridden at the task queue level. ```APIDOC ## Plugin Management ### Description Plugins like `SerializationPlugin` or `CodecPlugin` can be installed globally or scoped to specific task queues. ### Application-Level Plugin Example ```kotlin embeddedTemporal(module = { install(SerializationPlugin) { json { ignoreUnknownKeys = true } } taskQueue("my-queue") { workflow() } }) ``` ### Task Queue Override Example ```kotlin taskQueue("debug-queue") { install(SerializationPlugin) { json { prettyPrint = true } } workflow() } ``` -------------------------------- ### Run Kotlin Application with Gradle Source: https://github.com/snipesy/temporal-kt/blob/main/docs/GETTING_STARTED.md Execute the main Kotlin application using the Gradle wrapper. This command starts the embedded Temporal server and registers workflows/activities. ```bash ./gradlew run ``` -------------------------------- ### Basic Workflow Test Source: https://github.com/snipesy/temporal-kt/blob/main/docs/TESTING.md Demonstrates a basic test for a Temporal workflow. Configure your application, start a workflow, and assert its result. ```kotlin import com.surrealdev.temporal.testing.runTemporalTest import com.surrealdev.temporal.client.startWorkflow import com.surrealdev.temporal.workflow.result import kotlin.test.Test import kotlin.test.assertEquals class MyWorkflowTest { @Test fun `test workflow`() = runTemporalTest { // Configure workflows and activities application { taskQueue("test-queue") { workflow() activity(MyActivities()) } } // Start workflow val handle = client().startWorkflow( workflowType = "MyWorkflow", taskQueue = "test-queue", workflowId = "test-wf-123", arg = "input" ) // Wait for result val result: String = handle.result() assertEquals("expected", result) } } ``` -------------------------------- ### Configure Temporal Plugin in Gradle Source: https://github.com/snipesy/temporal-kt/blob/main/docs/COMPILER_PLUGIN.md Initial setup for the Temporal plugin within the build.gradle.kts file. ```kotlin // build.gradle.kts plugins { id("com.surrealdev.temporal") version "x.y.z" } temporal { compiler { enabled = true // Default: false - must explicitly enable } native { enabled = true // Default: true - auto-detects platform } } ``` -------------------------------- ### Connecting a Standalone Temporal Client in Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md Configure and connect a standalone Temporal client, including setting the target, namespace, and installing plugins like serialization and codecs. ```kotlin val client = TemporalClient.connect { target = "localhost:7233" namespace = "default" // Serialization and codec plugins work here too install(SerializationPlugin) { json { prettyPrint = true } } install(CodecPlugin) { compression() } // Client interceptors install(MyPlugin) { enabled = true } } ``` -------------------------------- ### Query a Running Workflow from Client Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Connect to Temporal and start a workflow, then use the workflow handle's `query` method to retrieve state information. Ensure the workflow is running before querying. ```kotlin fun main() = runBlocking { val client = TemporalClient.connect { target = "localhost:7233" namespace = "default" } val handle = client.startWorkflow( CounterWorkflow::class, "counter-queue", ) // Query the running workflow delay(500.milliseconds) val currentCount = handle.query("getCounter") println("Current counter: $currentCount") val status = handle.query("getStatus") println("Status: $status") } ``` -------------------------------- ### Install CodecPlugin with Custom Codec Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0011-payload-codec.md Integrates a custom `EncryptionCodec` into the Temporal application using the `CodecPlugin`. This ensures payloads are encrypted before being sent. ```kotlin fun TemporalApplication.module() { install(CodecPlugin) { custom(EncryptionCodec( keyId = "production-key", keyProvider = { kmsClient.getKey("production-key") } )) } // Or with chaining install(CodecPlugin) { chained { compression(threshold = 512) codec(EncryptionCodec(keyId = "production-key") { kmsClient.getKey(it) }) } } taskQueue("secure-queue") { workflow() } } ``` -------------------------------- ### Initialize Specialized Workers Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0005-modules.md Load specific combinations of modules when starting a worker application. ```kotlin // worker-orders/src/main/kotlin/Main.kt fun main() = temporalApplication { ordersModule() }.start() // worker-notifications/src/main/kotlin/Main.kt fun main() = temporalApplication { notificationsModule() }.start() // worker-all/src/main/kotlin/Main.kt fun main() = temporalApplication { ordersModule() notificationsModule() analyticsModule() }.start() ``` -------------------------------- ### Start GreetingWorkflow using Temporal CLI Source: https://github.com/snipesy/temporal-kt/blob/main/docs/GETTING_STARTED.md Initiate a workflow execution via the Temporal CLI. This command specifies the task queue, workflow type, and input payload. ```bash temporal workflow start \ --task-queue hello-world-queue \ --type GreetingWorkflow \ --input '"World"' ``` -------------------------------- ### Standard Project Installation with Jib Plugin Source: https://github.com/snipesy/temporal-kt/blob/main/plugins/jib/README.md Configure your standard Gradle project to use the Temporal Jib plugin. Ensure the plugin is added to the buildscript dependencies and applied along with the Jib and Temporal plugins. The plugin extension must be explicitly configured. ```kotlin // build.gradle.kts buildscript { dependencies { classpath("com.surrealdev.temporal:jib-plugin:VERSION") } } plugins { id("com.google.cloud.tools.jib") id("com.surrealdev.temporal") } jib { pluginExtensions { pluginExtension { implementation = "com.surrealdev.temporal.gradle.jib.TemporalJibExtension" } } } ``` -------------------------------- ### Share Dependencies Across Modules Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0005-modules.md Install plugins and provide shared dependencies within a base module that other modules can include. ```kotlin fun TemporalApplication.baseModule() { install(SerializationPlugin) { json { prettyPrint = true } } dependencies { provide { SlfLogger() } provide { PrometheusClient() } } } fun TemporalApplication.ordersModule() { baseModule() // Include shared config taskQueue("orders") { workflow() } } ``` -------------------------------- ### Module Registration for Temporal Application Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0003-inline.md Register workflows and activities within a Temporal application's task queue configuration. This example shows binding a workflow and two activities. ```kotlin fun TemporalApplication.myMainModule() { taskQueue("my-task-queue") { __MyWorkflow.bind(workflowRegistry) __MyActivity.bind(activityRegistry) __UnnestedActivity.bind(activityRegistry) } } ``` -------------------------------- ### Generated Workflow Stub Example Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0002-interfaces.md Illustrates how a compiler plugin can generate type-safe workflow stubs from annotated classes, enabling clients to interact with workflows using interfaces. ```kotlin // User writes: @Workflow("MyWorkflow") class MyWorkflow { ... } // Compiler plugin generates: @Workflow("MyWorkflow") interface MyWorkflowStub { @WorkflowRun suspend fun execute(arg: WorkflowArg): String } // Client can use: val stub = client.newWorkflowStub(...) stub.execute(arg) ``` -------------------------------- ### Troubleshoot Version Mismatch Errors Source: https://github.com/snipesy/temporal-kt/blob/main/docs/COMPILER_PLUGIN.md Example error messages encountered during Kotlin version mismatches and the configuration to disable the plugin as a workaround. ```text NoSuchMethodError: org.jetbrains.kotlin.gradle.plugin.KotlinCompilation... ClassNotFoundException: org.jetbrains.kotlin.ir.declarations... ``` ```kotlin temporal { compiler { enabled = false } } ``` -------------------------------- ### Provide Global Dependencies Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0004-dependency-injection.md Define global dependencies for your Temporal application using the `dependencies` block within `TemporalApplication.myMainModule`. Use `provide` to register services, optionally with factories that can access other provided services using `get()`. ```kotlin fun TemporalApplication.myMainModule() { dependencies { provide { SomeServiceImpl() } provide<() -> AnotherService> { { AnotherServiceImpl(get()) } } } // task queues, workflows, activities... } ``` -------------------------------- ### Call Activities from a Workflow Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Call activities from within a workflow using the workflow() context. This example demonstrates calling both a simple activity by function reference and another by activity type string with multiple arguments. ```kotlin import kotlin.time.Duration.Companion.seconds @Workflow("GreetingWorkflow") class GreetingWorkflow { @WorkflowRun suspend fun run(name: String): String { // Using function reference - cleaner API with payload-based handles val greeting = workflow() .startActivity( GreetingActivity::formatGreeting, arg = name, scheduleToCloseTimeout = 10.seconds, ).result() // Using activity type string with multiple arguments val localized = workflow() .startActivity( activityType = "getLocalizedGreeting", arg1 = name, arg2 = "es", scheduleToCloseTimeout = 10.seconds, ).result() return "$greeting ($localized)" } } ``` -------------------------------- ### Client Interaction with Generated Workflow Stub Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0003-inline.md Demonstrates how client code interacts with a generated workflow stub to start a workflow, invoke signals, and execute queries. This code is identical whether the workflow was defined declaratively or imperatively. ```kotlin val stub = OrderWorkflowStub(client) val handle = stub.start(OrderRequest(id = "order-123", total = 99.99)) val status = handle.status() // typed query handle.cancel("out of stock") // typed signal val result = handle.result() // typed result ``` -------------------------------- ### Query Workflows Using Temporal CLI Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Use the Temporal CLI to list workflows based on various search attribute criteria. Examples include filtering by customer ID, order count, creation time, tags, and full-text search within descriptions. ```bash # Find workflows by customer ID temporal workflow list -q "CustomerId = 'cust-123'" # Find premium customers with high order counts temporal workflow list -q "IsPremium = true AND OrderCount > 10" # Find workflows created in the last hour temporal workflow list -q "CreatedAt > '2024-01-15T10:00:00Z'" # Find workflows with specific tags temporal workflow list -q "'urgent' IN Tags" # Full-text search in description temporal workflow list -q "Description LIKE '%important%'" ``` -------------------------------- ### Activity Logger Example Source: https://github.com/snipesy/temporal-kt/blob/main/plugins/opentelemetry/README.md Use the activity logger extension to get a logger automatically named after the activity type. This logger automatically populates MDC values like activity ID, workflow ID, and task queue. ```kotlin class PaymentActivity { @Activity("chargeCard") suspend fun ActivityContext.charge(amount: Int): Boolean { val log = logger() // Logger named "temporal.activity.chargeCard" log.info("Charging {} cents", amount) // ... activity logic return true } } ``` -------------------------------- ### Workflow Logger Example Source: https://github.com/snipesy/temporal-kt/blob/main/plugins/opentelemetry/README.md Use the workflow logger extension to get a logger automatically named after the workflow type. This logger automatically populates MDC values like workflow ID, run ID, and task queue. ```kotlin @Workflow("OrderWorkflow") class OrderWorkflow { @WorkflowRun suspend fun run(orderId: String): String { val log = workflow().logger() // Logger named "temporal.workflow.OrderWorkflow" log.info("Processing order: {}", orderId) // ... workflow logic return "completed" } } ``` -------------------------------- ### Configure Temporal Application Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CODECS_AND_SERIALIZATION.md Set up serialization and codec plugins within a main function or a configuration-driven module. ```kotlin fun main() { embeddedTemporal(module = { install(SerializationPlugin) { json { ignoreUnknownKeys = true } } install(CodecPlugin) { compression(threshold = 1024) } taskQueue("orders-queue") { workflow() activity(OrderActivity()) } }).start(wait = true) } ``` ```kotlin fun TemporalApplication.ordersModule() { install(SerializationPlugin) { json { ignoreUnknownKeys = true } } install(CodecPlugin) { compression(threshold = 1024) } taskQueue("orders-queue") { workflow() activity(OrderActivity()) } } ``` -------------------------------- ### Installing a Scoped Plugin at Application Level in Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md Install a scoped plugin, like the MetricsPlugin, at the application level to apply it to all task queues within the application. ```kotlin app.install(MetricsPlugin) { enabled = true } ``` -------------------------------- ### Ktor Routing with Temporal Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0013-ktor-subroutine.md Demonstrates setting up Ktor with nested routing for HTTP endpoints, aligning with Temporal task queue configurations. This allows for versioned API paths like /v1/orders. ```kotlin fun TemporalApplication.ordersModule() { install(Ktor) { port = 8080 } taskQueue("orders") { route("v1") { // HTTP: POST /v1/orders post("/orders") { val request = call.receive() val handle = temporalClient.startWorkflow(request) call.respond(handle.workflowId) } // HTTP: GET /v1/orders/{id} get("/orders/{id}") { val id = call.parameters["id"]!! val handle = temporalClient.getWorkflowHandle(id) call.respond(handle.query(CreateOrderWorkflow::getStatus)) } // Workflow type: "v1/CreateOrder" workflow() activity(PaymentActivity()) } } } ``` -------------------------------- ### Start Child Workflows Sequentially in Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Start child workflows one by one within a parent workflow. This is useful when the execution of one child depends on the completion of another or for sequential processing. ```kotlin @Workflow("BatchProcessorWorkflow") class BatchProcessorWorkflow { @WorkflowRun suspend fun run(orderIds: List): List { val results = mutableListOf() for (orderId in orderIds) { // Start child workflow - payload-based handles for cleaner API val result = workflow().startChildWorkflow( workflowClass = ProcessOrderWorkflow::class, arg = orderId, options = ChildWorkflowOptions( workflowId = "process-$orderId", ), ).result() results.add(result) } return results } } ``` -------------------------------- ### Build Core-Bridge Module (Your Platform) Source: https://github.com/snipesy/temporal-kt/blob/main/core-bridge/README.md Use this command to build the core-bridge module on your local development platform. ```bash gradle build ``` -------------------------------- ### Installing a Scoped Plugin at Task-Queue Level in Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/PLUGINS.md Install a scoped plugin at a specific task queue level to apply it only to workflows running on that queue. This allows for fine-grained control and overrides application-level plugins. ```kotlin app.taskQueue("orders-queue") { install(MetricsPlugin) { enabled = true } workflow() } ``` -------------------------------- ### Run Config-Driven Application Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CONFIGURATION.md Use TemporalMain for applications driven entirely by configuration. ```kotlin fun main(args: Array) { TemporalMain.main(args) } ``` -------------------------------- ### PEM Certificate Format Source: https://github.com/snipesy/temporal-kt/blob/main/docs/AUTH_SETUP_GUIDE.md Example of the required PEM-encoded certificate structure. ```text -----BEGIN CERTIFICATE----- MIIBkTCB+wIJAKHBfpEgcM... -----END CERTIFICATE----- ``` -------------------------------- ### Build Core-Bridge Module (All Platforms) Source: https://github.com/snipesy/temporal-kt/blob/main/core-bridge/README.md Execute these commands to build the core-bridge module for all supported platforms, including native library compilation and copying. ```bash gradle cargoBuildAll gradle copyAllNativeLibs gradle build ``` -------------------------------- ### EncryptionCodec Implementation Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0011-payload-codec.md An example implementation of a PayloadCodec for encrypting and decrypting payloads using AES/GCM. ```APIDOC ## Encryption Codec ```kotlin class EncryptionCodec( private val keyId: String, private val keyProvider: suspend () -> SecretKey ) : PayloadCodec { companion object { private const val ENCODING_ENCRYPTED = "binary/encrypted" private val ENCODING_ENCRYPTED_BYTES = TemporalByteString.fromUtf8(ENCODING_ENCRYPTED) private val CIPHER_BYTES = TemporalByteString.fromUtf8("AES/GCM/NoPadding") private val KEY_ID_KEY = "encryption-key-id" } override suspend fun encode(payloads: TemporalPayloads): EncodedTemporalPayloads { val key = keyProvider() return EncodedTemporalPayloads(TemporalPayloads.of(payloads.payloads.map { payload -> val nonce = generateNonce() val encrypted = encrypt(payload.data, key, nonce) val meta = payload.metadataByteStrings.toMutableMap() meta[TemporalPayload.METADATA_ENCODING] = ENCODING_ENCRYPTED_BYTES meta["encryption-cipher"] = CIPHER_BYTES meta[KEY_ID_KEY] = TemporalByteString.fromUtf8(keyId) TemporalPayload.create(encrypted, meta) }).proto) } override suspend fun decode(payloads: EncodedTemporalPayloads): TemporalPayloads { val key = keyProvider() return TemporalPayloads.of(TemporalPayloads(payloads.proto).payloads.map { payload -> if (payload.encoding != ENCODING_ENCRYPTED) { return@map payload // Pass through non-encrypted } val decrypted = decrypt(payload.data, key) val meta = payload.metadataByteStrings.toMutableMap() meta.remove(TemporalPayload.METADATA_ENCODING) meta.remove("encryption-cipher") meta.remove(KEY_ID_KEY) TemporalPayload.create(decrypted, meta) }) } } ``` ``` -------------------------------- ### Start Child Workflow with Search Attributes Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Passes search attributes to a child workflow during its invocation. ```kotlin @Workflow("ParentWorkflow") class ParentWorkflow { @WorkflowRun suspend fun run(customerId: String): String { val childResult = workflow().startChildWorkflow( workflowType = "ChildWorkflow", arg = customerId, options = ChildWorkflowOptions( searchAttributes = searchAttributes { CUSTOMER_ID to customerId IS_PREMIUM to false } ) ).result() return "Parent completed: $childResult" } } ``` -------------------------------- ### Define YAML Configuration with Environment Variables Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CONFIGURATION.md Use environment variable substitution for connection settings. ```yaml temporal: connection: target: ${TEMPORAL_TARGET:-http://localhost:7233} namespace: ${TEMPORAL_NAMESPACE:-default} apiKey: ${TEMPORAL_API_KEY} ``` -------------------------------- ### Start Temporal Workers Source: https://github.com/snipesy/temporal-kt/blob/main/docs/JAVA_MIGRATE.md Register workflow and activity implementations with a worker factory to begin processing tasks. ```java // Java WorkflowClient client = WorkflowClient.newInstance(service); WorkerFactory factory = WorkerFactory.newInstance(client); Worker worker = factory.newWorker("my-task-queue"); worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); worker.registerActivitiesImplementations(new GreetingActivitiesImpl()); factory.start(); ``` ```kotlin // Kotlin fun main() { embeddedTemporal(module = { taskQueue("my-task-queue") { workflow() activity(GreetingActivities()) } }).start(wait = true) } ``` -------------------------------- ### Define an Application with Temporal Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Set up an embedded Temporal server and register workflows and activities for a specific task queue. This is typically done in the main application entry point. ```kotlin import com.surrealdev.temporal.application.embeddedTemporal import com.surrealdev.temporal.application.taskQueue fun main() { embeddedTemporal(configure = { connection { target = "localhost:7233" namespace = "default" } }) { taskQueue("hello-world-queue") { workflow() } } .start(wait = true) } ``` -------------------------------- ### Implement and Install Custom PayloadSerializer Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CODECS_AND_SERIALIZATION.md Provides full control over serialization by implementing PayloadSerializer, bypassing the converter chain. ```kotlin class MyCustomSerializer : PayloadSerializer { override fun serialize(typeInfo: KType, value: Any?): TemporalPayload { if (value == null) { return TemporalPayload.create( mapOf(TemporalPayload.METADATA_ENCODING to TemporalByteString.fromUtf8(TemporalPayload.ENCODING_NULL)) ) } val metadata = mapOf( TemporalPayload.METADATA_ENCODING to TemporalByteString.fromUtf8(TemporalPayload.ENCODING_BINARY), ) return TemporalPayload.create(metadata) { stream -> myEncode(value, stream) } } override fun deserialize(typeInfo: KType, payload: TemporalPayload): Any? { if (payload.encoding == TemporalPayload.ENCODING_NULL) return null return myDecode(typeInfo, payload.dataInputStream()) } } install(SerializationPlugin) { custom(MyCustomSerializer()) } ``` -------------------------------- ### Routing with Dependency Injection in Temporal-Kt Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0009-routing.md Illustrates how routes can be used to scope dependencies, allowing different configurations for the same workflow or activity based on the route. ```kotlin fun TemporalApplication.module() { taskQueue("main") { route("orders") { dependencies { provide { StripeGateway() } } workflow() activity(PaymentActivity()) } route("legacy") { dependencies { provide { LegacyGateway() } } workflow() // Same workflow, different gateway activity(PaymentActivity()) } } } ``` -------------------------------- ### StartChildWorkflowFailureCause Enum Source: https://github.com/snipesy/temporal-kt/blob/main/docs/FAILURES.md Enumerates possible causes for a child workflow failing to start. Useful for handling pre-execution errors. ```kotlin enum class StartChildWorkflowFailureCause { WORKFLOW_ALREADY_EXISTS, UNKNOWN, } ``` -------------------------------- ### Run Config-Driven Application via CLI Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CONFIGURATION.md Execute the application using a specific configuration file. ```bash java -jar myapp.jar -config=/path/to/config.yaml ``` -------------------------------- ### Implement a Workflow Stub Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0003-inline.md Defines a client-side workflow stub class that provides typed methods for starting and executing workflows. ```kotlin class MyWorkflowStub( private val client: TemporalClient, private val options: WorkflowStartOptions = WorkflowStartOptions(), ) : AbstractWorkflowStub { override fun build(client: TemporalClient, options: WorkflowStartOptions): MyWorkflowStub { return MyWorkflowStub(client, options) } /** Start and await result in one call. */ suspend fun execute(arg: WorkflowArg): String { return start(arg).result() } /** Start the workflow and return a typed handle for further interaction. */ fun start(arg: WorkflowArg): MyWorkflowHandle { val handle = client.startWorkflow( workflowType = "MyWorkflow", taskQueue = "my-task-queue", arg = arg, options = options, ) return MyWorkflowHandleImpl(handle) } companion object { /** Workflow type descriptor — single source of truth for method metadata. */ val descriptor = WorkflowDescriptor( workflowType = "MyWorkflow", taskQueue = "my-task-queue", argType = typeOf(), returnType = typeOf(), signals = listOf("approve"), queries = listOf("status"), ) /** Reflection-free factory for worker-side registration. */ fun newInstance(): __MyWorkflow = __MyWorkflow() } } ``` -------------------------------- ### Define Basic YAML Configuration Source: https://github.com/snipesy/temporal-kt/blob/main/docs/CONFIGURATION.md Standard connection settings for a Temporal application. ```yaml temporal: connection: target: "localhost:7233" namespace: "default" modules: - com.example.ModulesKt.ordersModule ``` -------------------------------- ### Explicit WorkflowContext in Temporal Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/proposals/TKT-0015-context-resolution.md Workflows currently require explicit WorkflowContext specification. This example shows the traditional approach. ```kotlin @Workflow("MyWorkflow") class MyWorkflow { @WorkflowRun suspend fun WorkflowContext.execute(arg: WorkflowArg): String { val greeting = activity().greet(arg.name) return "$greeting! Count: ${arg.count}" } } ``` -------------------------------- ### Continue As New to Different Workflow Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Transition a workflow to a different type while preserving state. ```kotlin @Workflow("MigratingWorkflow") class MigratingWorkflow { @WorkflowRun suspend fun run(state: OldState): String { val ctx = workflow() // Migrate to a new workflow type val newState = migrateState(state) ctx.continueAsNewTo( NewWorkflow::class, newState, ) // Never reached return "" } } ``` -------------------------------- ### Define a Child Workflow in Kotlin Source: https://github.com/snipesy/temporal-kt/blob/main/docs/WORKFLOWS_IMPERATIVE.md Define a reusable workflow that can be started as a child process. Ensure the workflow logic is self-contained and idempotent. ```kotlin @Workflow("ProcessOrderWorkflow") class ProcessOrderWorkflow { @WorkflowRun suspend fun run(orderId: String): OrderResult { // Child workflow logic workflow().sleep(1.seconds) return OrderResult(orderId = orderId, status = "processed") } } ```