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,34 @@
1
+ # This module provides a set of methods for imitating direct Child Workflow calls
2
+ # from within Workflows:
3
+ #
4
+ # class TestWorkflow < Temporal::Workflow
5
+ # def execute
6
+ # ChildWorkflow.execute!('foo', 'bar')
7
+ # end
8
+ # end
9
+ #
10
+ # This is analogous to calling:
11
+ #
12
+ # workflow.execute_workflow(ChildWorkflow, 'foo', 'bar')
13
+ #
14
+ require 'temporal/thread_local_context'
15
+
16
+ module Temporal
17
+ class Workflow
18
+ module ConvenienceMethods
19
+ def execute(*input, **args)
20
+ context = Temporal::ThreadLocalContext.get
21
+ raise 'Called Workflow#execute outside of a Workflow context' unless context
22
+
23
+ context.execute_workflow(self, *input, **args)
24
+ end
25
+
26
+ def execute!(*input, **args)
27
+ context = Temporal::ThreadLocalContext.get
28
+ raise 'Called Workflow#execute! outside of a Workflow context' unless context
29
+
30
+ context.execute_workflow!(self, *input, **args)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ module Temporal
2
+ class Workflow
3
+ class Dispatcher
4
+ WILDCARD = '*'.freeze
5
+
6
+ def initialize
7
+ @handlers = Hash.new { |hash, key| hash[key] = [] }
8
+ end
9
+
10
+ def register_handler(target, event_name, &handler)
11
+ handlers[target] << [event_name, handler]
12
+ end
13
+
14
+ def dispatch(target, event_name, args = nil)
15
+ handlers_for(target, event_name).each do |handler|
16
+ handler.call(*args)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :handlers
23
+
24
+ def handlers_for(target, event_name)
25
+ handlers[target]
26
+ .select { |(name, _)| name == event_name || name == WILDCARD }
27
+ .map(&:last)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ module Temporal
2
+ class Workflow
3
+ class ExecutionInfo < Struct.new(:workflow, :workflow_id, :run_id, :start_time, :close_time, :status, :history_length, keyword_init: true)
4
+ RUNNING_STATUS = :RUNNING
5
+ COMPLETED_STATUS = :COMPLETED
6
+ FAILED_STATUS = :FAILED
7
+ CANCELED_STATUS = :CANCELED
8
+ TERMINATED_STATUS = :TERMINATED
9
+ CONTINUED_AS_NEW_STATUS = :CONTINUED_AS_NEW
10
+ TIMED_OUT_STATUS = :TIMED_OUT
11
+
12
+ API_STATUS_MAP = {
13
+ WORKFLOW_EXECUTION_STATUS_RUNNING: RUNNING_STATUS,
14
+ WORKFLOW_EXECUTION_STATUS_COMPLETED: COMPLETED_STATUS,
15
+ WORKFLOW_EXECUTION_STATUS_FAILED: FAILED_STATUS,
16
+ WORKFLOW_EXECUTION_STATUS_CANCELED: CANCELED_STATUS,
17
+ WORKFLOW_EXECUTION_STATUS_TERMINATED: TERMINATED_STATUS,
18
+ WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW: CONTINUED_AS_NEW_STATUS,
19
+ WORKFLOW_EXECUTION_STATUS_TIMED_OUT: TIMED_OUT_STATUS
20
+ }.freeze
21
+
22
+ VALID_STATUSES = [
23
+ RUNNING_STATUS,
24
+ COMPLETED_STATUS,
25
+ FAILED_STATUS,
26
+ CANCELED_STATUS,
27
+ TERMINATED_STATUS,
28
+ CONTINUED_AS_NEW_STATUS,
29
+ TIMED_OUT_STATUS
30
+ ].freeze
31
+
32
+ def self.generate_from(response)
33
+ new(
34
+ workflow: response.type.name,
35
+ workflow_id: response.execution.workflow_id,
36
+ run_id: response.execution.run_id,
37
+ start_time: response.start_time&.to_time,
38
+ close_time: response.close_time&.to_time,
39
+ status: API_STATUS_MAP.fetch(response.status),
40
+ history_length: response.history_length,
41
+ ).freeze
42
+ end
43
+
44
+ VALID_STATUSES.each do |status|
45
+ define_method("#{status.downcase}?") do
46
+ self.status == status
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ require 'fiber'
2
+
3
+ require 'temporal/workflow/dispatcher'
4
+ require 'temporal/workflow/state_manager'
5
+ require 'temporal/workflow/context'
6
+ require 'temporal/workflow/history/event_target'
7
+
8
+ module Temporal
9
+ class Workflow
10
+ class Executor
11
+ def initialize(workflow_class, history)
12
+ @workflow_class = workflow_class
13
+ @dispatcher = Dispatcher.new
14
+ @state_manager = StateManager.new(dispatcher)
15
+ @history = history
16
+ end
17
+
18
+ def run
19
+ dispatcher.register_handler(
20
+ History::EventTarget.workflow,
21
+ 'started',
22
+ &method(:execute_workflow)
23
+ )
24
+
25
+ while window = history.next_window
26
+ state_manager.apply(window)
27
+ end
28
+
29
+ return state_manager.commands
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :workflow_class, :dispatcher, :state_manager, :history
35
+
36
+ def execute_workflow(input, metadata)
37
+ context = Workflow::Context.new(state_manager, dispatcher, metadata)
38
+
39
+ Fiber.new do
40
+ workflow_class.execute_in_context(context, input)
41
+ end.resume
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,77 @@
1
+ require 'fiber'
2
+
3
+ module Temporal
4
+ class Workflow
5
+ class Future
6
+ attr_reader :target, :callbacks
7
+
8
+ def initialize(target, context, cancelation_id: nil)
9
+ @target = target
10
+ @context = context
11
+ @cancelation_id = cancelation_id
12
+ @callbacks = []
13
+ @ready = false
14
+ @failed = false
15
+ @result = nil
16
+ @exception = nil
17
+ end
18
+
19
+ def finished?
20
+ ready? || failed?
21
+ end
22
+
23
+ def ready?
24
+ @ready
25
+ end
26
+
27
+ def failed?
28
+ @failed
29
+ end
30
+
31
+ def wait
32
+ return if finished?
33
+ context.wait_for(self)
34
+ end
35
+
36
+ def get
37
+ wait
38
+ exception || result
39
+ end
40
+
41
+ def set(result)
42
+ raise 'can not fulfil a failed future' if failed?
43
+
44
+ @result = result
45
+ @ready = true
46
+ end
47
+
48
+ def fail(exception)
49
+ raise 'can not fail a fulfilled future' if ready?
50
+
51
+ @exception = exception
52
+ @failed = true
53
+ end
54
+
55
+ def done(&block)
56
+ # do nothing
57
+ return if failed?
58
+
59
+ if ready?
60
+ block.call(result)
61
+ else
62
+ callbacks << block
63
+ end
64
+ end
65
+
66
+ def cancel
67
+ return false if finished?
68
+
69
+ context.cancel(target, cancelation_id)
70
+ end
71
+
72
+ private
73
+
74
+ attr_reader :context, :cancelation_id, :result, :exception
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,76 @@
1
+ require 'temporal/workflow/history/event'
2
+ require 'temporal/workflow/history/window'
3
+
4
+ module Temporal
5
+ class Workflow
6
+ class History
7
+ attr_reader :events
8
+
9
+ def initialize(events)
10
+ @events = events.map { |event| History::Event.new(event) }
11
+ @iterator = @events.each
12
+ end
13
+
14
+ def last_completed_workflow_task
15
+ events.select { |event| event.type == 'WORKFLOW_TASK_COMPLETED' }.last
16
+ end
17
+
18
+ # It is very important to replay the History window by window in order to
19
+ # simulate the exact same state the workflow was in when it processed the
20
+ # workflow task for the first time.
21
+ #
22
+ # A history window consists of 3 parts:
23
+ #
24
+ # 1. Events that happened since the last window (timer fired, activity completed, etc)
25
+ # 2. A workflow task related events (workflow task started, completed, failed, etc)
26
+ # 3. Commands issued by the last workflow task (^) (schedule activity, start timer, etc)
27
+ #
28
+ def next_window
29
+ return unless peek_event
30
+
31
+ window = History::Window.new
32
+
33
+ while event = next_event
34
+ window.add(event)
35
+
36
+ break if event.type == 'WORKFLOW_TASK_COMPLETED'
37
+ end
38
+
39
+ # Find the end of the window by exhausting all the commands
40
+ window.add(next_event) while command?(peek_event)
41
+
42
+ window.freeze
43
+ end
44
+
45
+ private
46
+
47
+ COMMAND_EVENT_TYPES = %w[
48
+ ACTIVITY_TASK_SCHEDULED
49
+ ACTIVITY_TASK_CANCEL_REQUESTED
50
+ TIMER_STARTED
51
+ CANCEL_TIMER_FAILED
52
+ TIMER_CANCELED
53
+ WORKFLOW_EXECUTION_CANCEL_REQUESTED
54
+ START_CHILD_WORKFLOW_EXECUTION_INITIATED
55
+ SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED
56
+ REQUEST_CANCEL_ACTIVITY_TASK_FAILED
57
+ REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED
58
+ MARKER_RECORDED
59
+ ].freeze
60
+
61
+ attr_reader :iterator
62
+
63
+ def next_event
64
+ iterator.next rescue nil
65
+ end
66
+
67
+ def peek_event
68
+ iterator.peek rescue nil
69
+ end
70
+
71
+ def command?(event)
72
+ COMMAND_EVENT_TYPES.include?(event&.type)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,69 @@
1
+ module Temporal
2
+ class Workflow
3
+ class History
4
+ class Event
5
+ EVENT_TYPES = %w[
6
+ ACTIVITY_TASK_STARTED
7
+ ACTIVITY_TASK_COMPLETED
8
+ ACTIVITY_TASK_FAILED
9
+ ACTIVITY_TASK_TIMED_OUT
10
+ ACTIVITY_TASK_CANCELED
11
+ TIMER_FIRED
12
+ REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED
13
+ WORKFLOW_EXECUTION_SIGNALED
14
+ WORKFLOW_EXECUTION_TERMINATED
15
+ SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED
16
+ EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED
17
+ EXTERNAL_WORKFLOW_EXECUTION_SIGNALED
18
+ UPSERT_WORKFLOW_SEARCH_ATTRIBUTES
19
+ ].freeze
20
+
21
+ CHILD_WORKFLOW_EVENTS = %w[
22
+ START_CHILD_WORKFLOW_EXECUTION_FAILED
23
+ CHILD_WORKFLOW_EXECUTION_STARTED
24
+ CHILD_WORKFLOW_EXECUTION_COMPLETED
25
+ CHILD_WORKFLOW_EXECUTION_FAILED
26
+ CHILD_WORKFLOW_EXECUTION_CANCELED
27
+ CHILD_WORKFLOW_EXECUTION_TIMED_OUT
28
+ CHILD_WORKFLOW_EXECUTION_TERMINATED
29
+ ].freeze
30
+
31
+ PREFIX = 'EVENT_TYPE_'.freeze
32
+
33
+ attr_reader :id, :timestamp, :type, :attributes
34
+
35
+ def initialize(raw_event)
36
+ @id = raw_event.event_id
37
+ @timestamp = raw_event.event_time.to_time
38
+ @type = raw_event.event_type.to_s.gsub(PREFIX, '')
39
+ @attributes = extract_attributes(raw_event)
40
+
41
+ freeze
42
+ end
43
+
44
+ # Returns the ID of the first event associated with the current event.
45
+ def originating_event_id
46
+ case type
47
+ when 'TIMER_FIRED'
48
+ attributes.started_event_id
49
+ when 'WORKFLOW_EXECUTION_SIGNALED'
50
+ 1 # fixed id for everything related to current workflow
51
+ when *EVENT_TYPES
52
+ attributes.scheduled_event_id
53
+ when *CHILD_WORKFLOW_EVENTS
54
+ attributes.initiated_event_id
55
+ else
56
+ id
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def extract_attributes(raw_event)
63
+ attributes_name = raw_event.attributes
64
+ raw_event.public_send(attributes_name)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,75 @@
1
+ require 'temporal/errors'
2
+
3
+ module Temporal
4
+ class Workflow
5
+ class History
6
+ class EventTarget
7
+ class UnexpectedEventType < InternalError; end
8
+
9
+ ACTIVITY_TYPE = :activity
10
+ CANCEL_ACTIVITY_REQUEST_TYPE = :cancel_activity_request
11
+ TIMER_TYPE = :timer
12
+ CANCEL_TIMER_REQUEST_TYPE = :cancel_timer_request
13
+ CHILD_WORKFLOW_TYPE = :child_workflow
14
+ MARKER_TYPE = :marker
15
+ EXTERNAL_WORKFLOW_TYPE = :external_workflow
16
+ CANCEL_EXTERNAL_WORKFLOW_REQUEST_TYPE = :cancel_external_workflow_request
17
+ WORKFLOW_TYPE = :workflow
18
+ CANCEL_WORKFLOW_REQUEST_TYPE = :cancel_workflow_request
19
+
20
+ TARGET_TYPES = {
21
+ 'ACTIVITY_TASK' => ACTIVITY_TYPE,
22
+ 'ACTIVITY_TASK_CANCEL' => CANCEL_ACTIVITY_REQUEST_TYPE,
23
+ 'REQUEST_CANCEL_ACTIVITY_TASK' => CANCEL_ACTIVITY_REQUEST_TYPE,
24
+ 'TIMER' => TIMER_TYPE,
25
+ 'CANCEL_TIMER' => CANCEL_TIMER_REQUEST_TYPE,
26
+ 'CHILD_WORKFLOW_EXECUTION' => CHILD_WORKFLOW_TYPE,
27
+ 'START_CHILD_WORKFLOW_EXECUTION' => CHILD_WORKFLOW_TYPE,
28
+ 'MARKER' => MARKER_TYPE,
29
+ 'EXTERNAL_WORKFLOW_EXECUTION' => EXTERNAL_WORKFLOW_TYPE,
30
+ 'SIGNAL_EXTERNAL_WORKFLOW_EXECUTION' => EXTERNAL_WORKFLOW_TYPE,
31
+ 'EXTERNAL_WORKFLOW_EXECUTION_CANCEL' => CANCEL_EXTERNAL_WORKFLOW_REQUEST_TYPE,
32
+ 'REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION' => CANCEL_EXTERNAL_WORKFLOW_REQUEST_TYPE,
33
+ 'UPSERT_WORKFLOW_SEARCH_ATTRIBUTES' => WORKFLOW_TYPE,
34
+ 'WORKFLOW_EXECUTION' => WORKFLOW_TYPE,
35
+ 'WORKFLOW_EXECUTION_CANCEL' => CANCEL_WORKFLOW_REQUEST_TYPE,
36
+ }.freeze
37
+
38
+ attr_reader :id, :type
39
+
40
+ def self.workflow
41
+ @workflow ||= new(1, WORKFLOW_TYPE)
42
+ end
43
+
44
+ def self.from_event(event)
45
+ _, target_type = TARGET_TYPES.find { |type, _| event.type.start_with?(type) }
46
+
47
+ unless target_type
48
+ raise UnexpectedEventType, "Unexpected event #{event.type}"
49
+ end
50
+
51
+ new(event.originating_event_id, target_type)
52
+ end
53
+
54
+ def initialize(id, type)
55
+ @id = id
56
+ @type = type
57
+
58
+ freeze
59
+ end
60
+
61
+ def ==(other)
62
+ id == other.id && type == other.type
63
+ end
64
+
65
+ def eql?(other)
66
+ self == other
67
+ end
68
+
69
+ def hash
70
+ [id, type].hash
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end