temporal-ruby 0.0.0 → 0.0.1.pre.pre1

Sign up to get free protection for your applications and to get access to all the features.
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