temporal-ruby 0.0.0 → 0.0.1.pre.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -42
  3. data/lib/gen/temporal/api/command/v1/message_pb.rb +146 -0
  4. data/lib/gen/temporal/api/common/v1/message_pb.rb +67 -0
  5. data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +35 -0
  6. data/lib/gen/temporal/api/enums/v1/common_pb.rb +34 -0
  7. data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +62 -0
  8. data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +60 -0
  9. data/lib/gen/temporal/api/enums/v1/namespace_pb.rb +31 -0
  10. data/lib/gen/temporal/api/enums/v1/query_pb.rb +31 -0
  11. data/lib/gen/temporal/api/enums/v1/task_queue_pb.rb +30 -0
  12. data/lib/gen/temporal/api/enums/v1/workflow_pb.rb +82 -0
  13. data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +55 -0
  14. data/lib/gen/temporal/api/failure/v1/message_pb.rb +81 -0
  15. data/lib/gen/temporal/api/filter/v1/message_pb.rb +38 -0
  16. data/lib/gen/temporal/api/history/v1/message_pb.rb +423 -0
  17. data/lib/gen/temporal/api/namespace/v1/message_pb.rb +55 -0
  18. data/lib/gen/temporal/api/query/v1/message_pb.rb +36 -0
  19. data/lib/gen/temporal/api/replication/v1/message_pb.rb +27 -0
  20. data/lib/gen/temporal/api/taskqueue/v1/message_pb.rb +60 -0
  21. data/lib/gen/temporal/api/version/v1/message_pb.rb +28 -0
  22. data/lib/gen/temporal/api/workflow/v1/message_pb.rb +83 -0
  23. data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +538 -0
  24. data/lib/gen/temporal/api/workflowservice/v1/service_pb.rb +19 -0
  25. data/lib/gen/temporal/api/workflowservice/v1/service_services_pb.rb +223 -0
  26. data/lib/temporal-ruby.rb +1 -0
  27. data/lib/temporal.rb +137 -0
  28. data/lib/temporal/activity.rb +33 -0
  29. data/lib/temporal/activity/async_token.rb +34 -0
  30. data/lib/temporal/activity/context.rb +64 -0
  31. data/lib/temporal/activity/poller.rb +79 -0
  32. data/lib/temporal/activity/task_processor.rb +78 -0
  33. data/lib/temporal/activity/workflow_convenience_methods.rb +41 -0
  34. data/lib/temporal/client.rb +21 -0
  35. data/lib/temporal/client/errors.rb +8 -0
  36. data/lib/temporal/client/grpc_client.rb +345 -0
  37. data/lib/temporal/client/serializer.rb +31 -0
  38. data/lib/temporal/client/serializer/base.rb +23 -0
  39. data/lib/temporal/client/serializer/cancel_timer.rb +19 -0
  40. data/lib/temporal/client/serializer/complete_workflow.rb +20 -0
  41. data/lib/temporal/client/serializer/fail_workflow.rb +20 -0
  42. data/lib/temporal/client/serializer/failure.rb +29 -0
  43. data/lib/temporal/client/serializer/payload.rb +25 -0
  44. data/lib/temporal/client/serializer/record_marker.rb +23 -0
  45. data/lib/temporal/client/serializer/request_activity_cancellation.rb +19 -0
  46. data/lib/temporal/client/serializer/schedule_activity.rb +53 -0
  47. data/lib/temporal/client/serializer/start_child_workflow.rb +51 -0
  48. data/lib/temporal/client/serializer/start_timer.rb +20 -0
  49. data/lib/temporal/concerns/executable.rb +37 -0
  50. data/lib/temporal/concerns/typed.rb +40 -0
  51. data/lib/temporal/configuration.rb +44 -0
  52. data/lib/temporal/errors.rb +38 -0
  53. data/lib/temporal/executable_lookup.rb +25 -0
  54. data/lib/temporal/execution_options.rb +35 -0
  55. data/lib/temporal/json.rb +18 -0
  56. data/lib/temporal/metadata.rb +68 -0
  57. data/lib/temporal/metadata/activity.rb +27 -0
  58. data/lib/temporal/metadata/base.rb +17 -0
  59. data/lib/temporal/metadata/workflow.rb +22 -0
  60. data/lib/temporal/metadata/workflow_task.rb +25 -0
  61. data/lib/temporal/metrics.rb +37 -0
  62. data/lib/temporal/metrics_adapters/log.rb +33 -0
  63. data/lib/temporal/metrics_adapters/null.rb +9 -0
  64. data/lib/temporal/middleware/chain.rb +30 -0
  65. data/lib/temporal/middleware/entry.rb +9 -0
  66. data/lib/temporal/retry_policy.rb +27 -0
  67. data/lib/temporal/saga/concern.rb +23 -0
  68. data/lib/temporal/saga/result.rb +22 -0
  69. data/lib/temporal/saga/saga.rb +24 -0
  70. data/lib/temporal/testing.rb +50 -0
  71. data/lib/temporal/testing/future_registry.rb +27 -0
  72. data/lib/temporal/testing/local_activity_context.rb +17 -0
  73. data/lib/temporal/testing/local_workflow_context.rb +178 -0
  74. data/lib/temporal/testing/temporal_override.rb +121 -0
  75. data/lib/temporal/testing/workflow_execution.rb +44 -0
  76. data/lib/temporal/testing/workflow_override.rb +36 -0
  77. data/lib/temporal/thread_local_context.rb +14 -0
  78. data/lib/temporal/thread_pool.rb +63 -0
  79. data/lib/temporal/types.rb +7 -0
  80. data/lib/temporal/uuid.rb +19 -0
  81. data/lib/temporal/version.rb +1 -1
  82. data/lib/temporal/worker.rb +88 -0
  83. data/lib/temporal/workflow.rb +42 -0
  84. data/lib/temporal/workflow/command.rb +39 -0
  85. data/lib/temporal/workflow/command_state_machine.rb +48 -0
  86. data/lib/temporal/workflow/context.rb +243 -0
  87. data/lib/temporal/workflow/convenience_methods.rb +34 -0
  88. data/lib/temporal/workflow/dispatcher.rb +31 -0
  89. data/lib/temporal/workflow/execution_info.rb +51 -0
  90. data/lib/temporal/workflow/executor.rb +45 -0
  91. data/lib/temporal/workflow/future.rb +77 -0
  92. data/lib/temporal/workflow/history.rb +76 -0
  93. data/lib/temporal/workflow/history/event.rb +69 -0
  94. data/lib/temporal/workflow/history/event_target.rb +75 -0
  95. data/lib/temporal/workflow/history/window.rb +40 -0
  96. data/lib/temporal/workflow/poller.rb +67 -0
  97. data/lib/temporal/workflow/replay_aware_logger.rb +36 -0
  98. data/lib/temporal/workflow/state_manager.rb +342 -0
  99. data/lib/temporal/workflow/task_processor.rb +78 -0
  100. data/rbi/temporal-ruby.rbi +43 -0
  101. data/temporal.gemspec +10 -2
  102. metadata +186 -6
@@ -0,0 +1,38 @@
1
+ module Temporal
2
+ # Superclass for all Temporal errors
3
+ class Error < StandardError; end
4
+
5
+ # Superclass for errors specific to Temporal worker itself
6
+ class InternalError < Error; end
7
+
8
+ # Superclass for misconfiguration/misuse on the client (user) side
9
+ class ClientError < Error; end
10
+
11
+ # Represents any timeout
12
+ class TimeoutError < ClientError; end
13
+
14
+ # A superclass for activity exceptions raised explicitly
15
+ # with the intent to propagate to a workflow
16
+ class ActivityException < ClientError; end
17
+
18
+ class ActivityNotRegistered < ClientError; end
19
+ class WorkflowNotRegistered < ClientError; end
20
+
21
+ class ApiError < Error; end
22
+
23
+ class NotFoundFailure < ApiError; end
24
+ class WorkflowExecutionAlreadyStartedFailure < ApiError
25
+ attr_reader :run_id
26
+
27
+ def initialize(message, run_id)
28
+ super(message)
29
+ @run_id = run_id
30
+ end
31
+ end
32
+ class NamespaceNotActiveFailure < ApiError; end
33
+ class ClientVersionNotSupportedFailure < ApiError; end
34
+ class FeatureVersionNotSupportedFailure < ApiError; end
35
+ class NamespaceAlreadyExistsFailure < ApiError; end
36
+ class CancellationAlreadyRequestedFailure < ApiError; end
37
+ class QueryFailedFailure < ApiError; end
38
+ end
@@ -0,0 +1,25 @@
1
+ # This class is responsible for matching an executable (activity or workflow) name
2
+ # to a class implementing it.
3
+ #
4
+ # TODO: This class should be responsible for handling executable versions
5
+ # when these are implemented
6
+ #
7
+ module Temporal
8
+ class ExecutableLookup
9
+ def initialize
10
+ @executables = {}
11
+ end
12
+
13
+ def add(name, executable)
14
+ executables[name] = executable
15
+ end
16
+
17
+ def find(name)
18
+ executables[name]
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :executables
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ require 'temporal/concerns/executable'
2
+
3
+ module Temporal
4
+ class ExecutionOptions
5
+ attr_reader :name, :namespace, :task_queue, :retry_policy, :timeouts, :headers
6
+
7
+ def initialize(object, options = {})
8
+ @name = options[:name] || object.to_s
9
+ @namespace = options[:namespace]
10
+ @task_queue = options[:task_queue] || options[:task_list]
11
+ @retry_policy = options[:retry_policy]
12
+ @timeouts = options[:timeouts] || {}
13
+ @headers = options[:headers] || {}
14
+
15
+ if object.singleton_class.included_modules.include?(Concerns::Executable)
16
+ @namespace ||= object.namespace
17
+ @task_queue ||= object.task_queue
18
+ @retry_policy ||= object.retry_policy
19
+ @timeouts = object.timeouts.merge(@timeouts) if object.timeouts
20
+ @headers = object.headers.merge(@headers) if object.headers
21
+ end
22
+
23
+ @namespace ||= Temporal.configuration.namespace
24
+ @task_queue ||= Temporal.configuration.task_queue
25
+ @timeouts = Temporal.configuration.timeouts.merge(@timeouts)
26
+ @headers = Temporal.configuration.headers.merge(@headers)
27
+
28
+ freeze
29
+ end
30
+
31
+ def task_list
32
+ @task_queue
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # Helper class for serializing/deserializing JSON
2
+ require 'oj'
3
+
4
+ module Temporal
5
+ module JSON
6
+ OJ_OPTIONS = {
7
+ mode: :object
8
+ }.freeze
9
+
10
+ def self.serialize(value)
11
+ Oj.dump(value, OJ_OPTIONS)
12
+ end
13
+
14
+ def self.deserialize(value)
15
+ Oj.load(value.to_s, OJ_OPTIONS)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ require 'temporal/errors'
2
+ require 'temporal/metadata/activity'
3
+ require 'temporal/metadata/workflow'
4
+ require 'temporal/metadata/workflow_task'
5
+
6
+ module Temporal
7
+ module Metadata
8
+ ACTIVITY_TYPE = :activity
9
+ WORKFLOW_TASK_TYPE = :workflow_task
10
+ WORKFLOW_TYPE = :workflow
11
+
12
+ class << self
13
+ def generate(type, data, namespace = nil)
14
+ case type
15
+ when ACTIVITY_TYPE
16
+ activity_metadata_from(data, namespace)
17
+ when WORKFLOW_TASK_TYPE
18
+ workflow_task_metadata_from(data, namespace)
19
+ when WORKFLOW_TYPE
20
+ workflow_metadata_from(data)
21
+ else
22
+ raise InternalError, 'Unsupported metadata type'
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def headers(fields)
29
+ fields.transform_values { |v| v[:data] }
30
+ end
31
+
32
+ def activity_metadata_from(task, namespace)
33
+ Metadata::Activity.new(
34
+ namespace: namespace,
35
+ id: task.activity_id,
36
+ name: task.activity_type.name,
37
+ task_token: task.task_token,
38
+ attempt: task.attempt,
39
+ workflow_run_id: task.workflow_execution.run_id,
40
+ workflow_id: task.workflow_execution.workflow_id,
41
+ workflow_name: task.workflow_type.name,
42
+ headers: headers(task.header&.fields.to_h)
43
+ )
44
+ end
45
+
46
+ def workflow_task_metadata_from(task, namespace)
47
+ Metadata::WorkflowTask.new(
48
+ namespace: namespace,
49
+ id: task.started_event_id,
50
+ task_token: task.task_token,
51
+ attempt: task.attempt,
52
+ workflow_run_id: task.workflow_execution.run_id,
53
+ workflow_id: task.workflow_execution.workflow_id,
54
+ workflow_name: task.workflow_type.name
55
+ )
56
+ end
57
+
58
+ def workflow_metadata_from(event)
59
+ Metadata::Workflow.new(
60
+ name: event.workflow_type.name,
61
+ run_id: event.original_execution_run_id,
62
+ attempt: event.attempt,
63
+ headers: headers(event.header&.fields.to_h)
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,27 @@
1
+ require 'temporal/metadata/base'
2
+
3
+ module Temporal
4
+ module Metadata
5
+ class Activity < Base
6
+ attr_reader :namespace, :id, :name, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name, :headers
7
+
8
+ def initialize(namespace:, id:, name:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:, headers: {})
9
+ @namespace = namespace
10
+ @id = id
11
+ @name = name
12
+ @task_token = task_token
13
+ @attempt = attempt
14
+ @workflow_run_id = workflow_run_id
15
+ @workflow_id = workflow_id
16
+ @workflow_name = workflow_name
17
+ @headers = headers
18
+
19
+ freeze
20
+ end
21
+
22
+ def activity?
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ module Temporal
2
+ module Metadata
3
+ class Base
4
+ def activity?
5
+ false
6
+ end
7
+
8
+ def workflow_task?
9
+ false
10
+ end
11
+
12
+ def workflow?
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ require 'temporal/metadata/base'
2
+
3
+ module Temporal
4
+ module Metadata
5
+ class Workflow < Base
6
+ attr_reader :name, :run_id, :attempt, :headers
7
+
8
+ def initialize(name:, run_id:, attempt:, headers: {})
9
+ @name = name
10
+ @run_id = run_id
11
+ @attempt = attempt
12
+ @headers = headers
13
+
14
+ freeze
15
+ end
16
+
17
+ def workflow?
18
+ true
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'temporal/metadata/base'
2
+
3
+ module Temporal
4
+ module Metadata
5
+ class WorkflowTask < Base
6
+ attr_reader :namespace, :id, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name
7
+
8
+ def initialize(namespace:, id:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:)
9
+ @namespace = namespace
10
+ @id = id
11
+ @task_token = task_token
12
+ @attempt = attempt
13
+ @workflow_run_id = workflow_run_id
14
+ @workflow_id = workflow_id
15
+ @workflow_name = workflow_name
16
+
17
+ freeze
18
+ end
19
+
20
+ def workflow_task?
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ module Temporal
2
+ class Metrics
3
+ def initialize(adapter)
4
+ @adapter = adapter
5
+ end
6
+
7
+ def increment(key, tags = {})
8
+ count(key, 1, tags)
9
+ end
10
+
11
+ def decrement(key, tags = {})
12
+ count(key, -1, tags)
13
+ end
14
+
15
+ def count(key, count, tags = {})
16
+ adapter.count(key, count, tags)
17
+ rescue StandardError => error
18
+ Temporal.logger.error("Adapter failed to send count metrics for #{key}: #{error.inspect}")
19
+ end
20
+
21
+ def gauge(key, value, tags = {})
22
+ adapter.gauge(key, value, tags)
23
+ rescue StandardError => error
24
+ Temporal.logger.error("Adapter failed to send gauge metrics for #{key}: #{error.inspect}")
25
+ end
26
+
27
+ def timing(key, time, tags = {})
28
+ adapter.timing(key, time, tags)
29
+ rescue StandardError => error
30
+ Temporal.logger.error("Adapter failed to send timing metrics for #{key}: #{error.inspect}")
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :adapter
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ module Temporal
2
+ module MetricsAdapters
3
+ class Log
4
+ def initialize(logger)
5
+ @logger = logger
6
+ end
7
+
8
+ def count(key, count, tags)
9
+ logger.debug(format_message(key, 'count', count, tags))
10
+ end
11
+
12
+ def gauge(key, value, tags)
13
+ logger.debug(format_message(key, 'gauge', value, tags))
14
+ end
15
+
16
+ def timing(key, time, tags)
17
+ logger.debug(format_message(key, 'timing', time, tags))
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :logger
23
+
24
+ def format_message(key, type, value, tags)
25
+ tags_str = tags.map { |k, v| "#{k}:#{v}" }.join(',')
26
+ parts = [key, type, value]
27
+ parts << tags_str if !tags_str.empty?
28
+
29
+ parts.join(' | ')
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Temporal
2
+ module MetricsAdapters
3
+ class Null
4
+ def count(_key, _count, _tags); end
5
+ def gauge(_key, _value, _tags); end
6
+ def timing(_key, _time, _tags); end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ module Temporal
2
+ module Middleware
3
+ class Chain
4
+ def initialize(entries = [])
5
+ @middleware = entries.map(&:init_middleware)
6
+ end
7
+
8
+ def invoke(metadata)
9
+ result = nil
10
+ chain = middleware.dup
11
+
12
+ traverse_chain = lambda do
13
+ if chain.empty?
14
+ result = yield
15
+ else
16
+ chain.shift.call(metadata, &traverse_chain)
17
+ end
18
+ end
19
+
20
+ traverse_chain.call
21
+
22
+ result
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :middleware
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ module Temporal
2
+ module Middleware
3
+ class Entry < Struct.new(:klass, :args)
4
+ def init_middleware
5
+ klass.new(*args)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ require 'temporal/errors'
2
+
3
+ module Temporal
4
+ class RetryPolicy < Struct.new(:interval, :backoff, :max_interval, :max_attempts,
5
+ :expiration_interval, :non_retriable_errors, keyword_init: true)
6
+
7
+ class InvalidRetryPolicy < ClientError; end
8
+
9
+ def validate!
10
+ unless interval && backoff
11
+ raise InvalidRetryPolicy, 'interval and backoff must be set'
12
+ end
13
+
14
+ unless max_attempts || expiration_interval
15
+ raise InvalidRetryPolicy, 'max_attempts or expiration_interval must be set'
16
+ end
17
+
18
+ unless [interval, max_interval, expiration_interval].compact.all? { |arg| arg.is_a?(Integer) }
19
+ raise InvalidRetryPolicy, 'All intervals must be specified in whole seconds'
20
+ end
21
+
22
+ unless [interval, max_interval, expiration_interval].compact.all? { |arg| arg > 0 }
23
+ raise InvalidRetryPolicy, 'All intervals must be greater than 0'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'temporal/saga/saga'
2
+ require 'temporal/saga/result'
3
+
4
+ module Temporal
5
+ module Saga
6
+ module Concern
7
+ def run_saga(&block)
8
+ saga = Temporal::Saga::Saga.new(workflow)
9
+
10
+ block.call(saga)
11
+
12
+ Result.new(true)
13
+ rescue StandardError => error # TODO: is there a need for a specialized error here?
14
+ logger.error("Saga execution aborted: #{error.inspect}")
15
+ logger.debug(error.backtrace.join("\n"))
16
+
17
+ saga.compensate
18
+
19
+ Result.new(false, error)
20
+ end
21
+ end
22
+ end
23
+ end