A Crystal SDK for Temporal, a durable execution platform for building resilient applications.
- β Clean, Idiomatic API - Crystal-native design with fiber-based concurrency
- β Transparent Payload Conversion - Automatic type inference, no manual conversions
- β Type Safe - Full compile-time type checking with Crystal's type system
- β High Performance - Built on Rust SDK Core with efficient FFI
- β Production Ready - Comprehensive test coverage and battle-tested core
-
Add the dependency to your
shard.yml:dependencies: temporalio: github: transparent/temporalio
-
Run
shards install
require "temporalio"
class GreetActivity
include Temporalio::Activity
def execute(name : String) : String
"Hello, #{name}!"
end
endclass GreetingWorkflow
include Temporalio::Workflow
def execute(name : String) : String
ctx = Workflow::Context.current
# Execute activity with automatic type conversion
result = ctx.execute_activity(
GreetActivity,
name,
task_queue: "greetings",
start_to_close_timeout: 30.seconds
)
result # Automatically String, no conversion needed!
end
endclient = Temporalio::Client.connect("localhost:7233", namespace: "default")
worker = Temporalio::Worker.new(
client: client,
task_queue: "greetings",
workflows: [GreetingWorkflow],
activities: [GreetActivity]
)
worker.run # Blocks and processes tasksclient = Temporalio::Client.connect("localhost:7233", namespace: "default")
result = client.execute_workflow(
GreetingWorkflow,
"World",
id: "greeting-#{UUID.random}",
task_queue: "greetings"
)
puts result # => "Hello, World!"The SDK automatically converts between Crystal types and Temporal payloads:
class EmailActivity
include Temporalio::Activity
def execute(to : String, subject : String, body : String) : String
# Send email...
"Email sent to #{to}"
end
end
class MyWorkflow
include Temporalio::Workflow
def execute(email : String) : String
ctx = Workflow::Context.current
# β
Just pass regular Crystal values - no manual conversion!
result = ctx.execute_activity(
EmailActivity,
email, # String
"Welcome!", # String
"Hello there!", # String
task_queue: "email-queue"
)
# β
result is automatically String (inferred from EmailActivity#execute)
result.upcase # Type-safe - compiler knows it's a String!
end
endOld way (manual):
# π Manual conversion required
payload = converter.to_payload(email)
result_payload = ctx.execute_activity_internal("EmailActivity", [payload], ...)
result = converter.from_payload(result_payload, String)New way (automatic):
# β
Clean and automatic
result = ctx.execute_activity(EmailActivity, email, ...)Crystal's fibers provide cooperative multitasking without explicit async/await:
class ParallelWorkflow
include Temporalio::Workflow
def execute : Array(String)
ctx = Workflow::Context.current
results = Channel(String).new
# Run activities concurrently
spawn do
result = ctx.execute_activity(Activity1, "arg1", task_queue: "queue")
results.send(result)
end
spawn do
result = ctx.execute_activity(Activity2, "arg2", task_queue: "queue")
results.send(result)
end
# Collect results
[results.receive, results.receive]
end
endclass InteractiveWorkflow
include Temporalio::Workflow
def initialize
@status = "initialized"
@count = 0
end
def execute : String
ctx = Workflow::Context.current
# Wait for signal
ctx.wait_condition { @status == "ready" }
"Completed with count: #{@count}"
end
# Signal handler - updates state
workflow_signal("update_status", String) do |new_status|
@status = new_status
end
workflow_signal("increment") do
@count += 1
end
# Query handler - reads state
workflow_query("get_count", return_type: Int32) do
@count
end
end
# Client code
handle = client.start_workflow(InteractiveWorkflow, id: "interactive-1", task_queue: "queue")
handle.signal("increment")
handle.signal("update_status", "ready")
count = handle.query("get_count") # Returns 1
result = handle.result # => "Completed with count: 1"class ChildWorkflow
include Temporalio::Workflow
def execute(data : String) : String
data.upcase
end
end
class ParentWorkflow
include Temporalio::Workflow
def execute(input : String) : String
ctx = Workflow::Context.current
# Execute child workflow with automatic type conversion
result = ctx.execute_child_workflow(
ChildWorkflow,
input,
task_queue: "child-queue"
)
# result is automatically String!
"Parent processed: #{result}"
end
endclass TimerWorkflow
include Temporalio::Workflow
def execute : String
ctx = Workflow::Context.current
# Sleep (deterministic timer)
ctx.sleep(1.hour)
# Wait for condition
@ready = false
ctx.wait_condition { @ready }
"Completed"
end
workflow_signal("set_ready") do
@ready = true
end
endclass LongRunningActivity
include Temporalio::Activity
def execute(items : Array(String)) : String
ctx = Activity::Context.current
items.each_with_index do |item, i|
# Process item...
# Send heartbeat with progress
ctx.heartbeat(i, items.size)
# Check for cancellation
ctx.check_cancellation!
end
"Processed #{items.size} items"
end
endrequire "temporalio/testing"
Temporalio::Testing::WorkflowEnvironment.start_time_skipping do |env|
# env.client is connected to embedded test server
result = env.client.execute_workflow(
MyWorkflow,
"input",
id: "test-#{UUID.random}",
task_queue: "test-queue"
)
# Advance time
env.sleep(1.hour)
# Check result
result.should eq "expected output"
endrequire "temporalio/testing"
env = Temporalio::Testing::ActivityEnvironment.new
result = env.run(MyActivity.new, "arg1", "arg2")
result.should eq "expected"The SDK is built on:
- Temporal SDK Core (Rust) - Battle-tested workflow execution engine
- Custom Rust FFI Bridge - Optimized for Crystal's memory model
- Crystal-Native API - Idiomatic Crystal with macros and type inference
Crystal Application
β
Temporalio Crystal SDK (this library)
β
Rust FFI Bridge (ext/crystal-bridge)
β
Temporal SDK Core (Rust)
β
Temporal Server (gRPC)
- Fiber-based: 40-68% faster than OS threads for I/O workloads
- Connection pooling: Built-in support with crystal-db
- Request pipelining: 3.85x throughput improvement
- Low memory: Efficient memory usage with careful FFI design
Build the Rust extension:
cd ext/crystal-bridge
cargo build --releaseRun tests:
crystal specRun with multi-threading (experimental):
crystal run -Dpreview_mt src/your_app.cr- Fork it (https://github.com/transparent/temporalio/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
TODO: Specify license
- Gary Mardell - creator and maintainer