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,22 @@
1
+ module Temporal
2
+ module Saga
3
+ class Result
4
+ attr_reader :rollback_reason
5
+
6
+ def initialize(completed, rollback_reason = nil)
7
+ @completed = completed
8
+ @rollback_reason = rollback_reason
9
+
10
+ freeze
11
+ end
12
+
13
+ def completed?
14
+ @completed
15
+ end
16
+
17
+ def compensated?
18
+ !completed?
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module Temporal
2
+ module Saga
3
+ class Saga
4
+ def initialize(context)
5
+ @context = context
6
+ @compensations = []
7
+ end
8
+
9
+ def add_compensation(activity, *args)
10
+ compensations << [activity, args]
11
+ end
12
+
13
+ def compensate
14
+ compensations.reverse_each do |(activity, args)|
15
+ context.execute_activity!(activity, *args)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :context, :compensations
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,50 @@
1
+ require 'temporal/testing/temporal_override'
2
+ require 'temporal/testing/workflow_override'
3
+
4
+ module Temporal
5
+ module Testing
6
+ DISABLED_MODE = nil
7
+ LOCAL_MODE = :local
8
+
9
+ class << self
10
+ def local!(&block)
11
+ set_mode(LOCAL_MODE, &block)
12
+ end
13
+
14
+ def disabled!(&block)
15
+ set_mode(DISABLED_MODE, &block)
16
+ end
17
+
18
+ def disabled?
19
+ mode == DISABLED_MODE
20
+ end
21
+
22
+ def local?
23
+ mode == LOCAL_MODE
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :mode
29
+
30
+ def set_mode(new_mode, &block)
31
+ if block_given?
32
+ with_mode(new_mode, &block)
33
+ else
34
+ @mode = new_mode
35
+ end
36
+ end
37
+
38
+ def with_mode(new_mode, &block)
39
+ previous_mode = mode
40
+ @mode = new_mode
41
+ yield
42
+ ensure
43
+ @mode = previous_mode
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ Temporal.singleton_class.prepend Temporal::Testing::TemporalOverride
50
+ Temporal::Workflow.extend Temporal::Testing::WorkflowOverride
@@ -0,0 +1,27 @@
1
+ module Temporal
2
+ module Testing
3
+ class FutureRegistry
4
+ def initialize
5
+ @store = {}
6
+ end
7
+
8
+ def register(token, future)
9
+ raise 'already registered' if store.key?(token)
10
+
11
+ store[token] = future
12
+ end
13
+
14
+ def complete(token, result)
15
+ store[token].set(result)
16
+ end
17
+
18
+ def fail(token, error)
19
+ store[token].fail(error.class.name, error.message)
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :store
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ require 'securerandom'
2
+ require 'temporal/uuid'
3
+ require 'temporal/activity/context'
4
+
5
+ module Temporal
6
+ module Testing
7
+ class LocalActivityContext < Activity::Context
8
+ def initialize(metadata)
9
+ super(nil, metadata)
10
+ end
11
+
12
+ def heartbeat(details = nil)
13
+ raise NotImplementedError, 'not yet available for testing'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,178 @@
1
+ require 'securerandom'
2
+ require 'temporal/testing/local_activity_context'
3
+ require 'temporal/testing/workflow_execution'
4
+ require 'temporal/execution_options'
5
+ require 'temporal/metadata/activity'
6
+ require 'temporal/workflow/future'
7
+ require 'temporal/workflow/history/event_target'
8
+
9
+ module Temporal
10
+ module Testing
11
+ class LocalWorkflowContext
12
+ attr_reader :headers
13
+
14
+ def initialize(execution, workflow_id, run_id, disabled_releases, headers = {})
15
+ @last_event_id = 0
16
+ @execution = execution
17
+ @run_id = run_id
18
+ @workflow_id = workflow_id
19
+ @disabled_releases = disabled_releases
20
+ @headers = headers
21
+ end
22
+
23
+ def logger
24
+ Temporal.logger
25
+ end
26
+
27
+ def has_release?(change_name)
28
+ !disabled_releases.include?(change_name.to_s)
29
+ end
30
+
31
+ def execute_activity(activity_class, *input, **args)
32
+ options = args.delete(:options) || {}
33
+ input << args unless args.empty?
34
+
35
+ event_id = next_event_id
36
+ activity_id = options[:activity_id] || event_id
37
+
38
+ target = Workflow::History::EventTarget.new(event_id, Workflow::History::EventTarget::ACTIVITY_TYPE)
39
+ future = Workflow::Future.new(target, self, cancelation_id: activity_id)
40
+
41
+ execution_options = ExecutionOptions.new(activity_class, options)
42
+ metadata = Metadata::Activity.new(
43
+ namespace: execution_options.namespace,
44
+ id: activity_id,
45
+ name: execution_options.name,
46
+ task_token: nil,
47
+ attempt: 1,
48
+ workflow_run_id: run_id,
49
+ workflow_id: workflow_id,
50
+ workflow_name: nil, # not yet used, but will be in the future
51
+ headers: execution_options.headers
52
+ )
53
+ context = LocalActivityContext.new(metadata)
54
+
55
+ result = activity_class.execute_in_context(context, input)
56
+
57
+ if context.async?
58
+ execution.register_future(context.async_token, future)
59
+ else
60
+ # Fulfil the future straigt away for non-async activities
61
+ future.set(result)
62
+ end
63
+
64
+ future
65
+ end
66
+
67
+ def execute_activity!(activity_class, *input, **args)
68
+ future = execute_activity(activity_class, *input, **args)
69
+ result = future.get
70
+
71
+ raise future.exception if future.failed?
72
+
73
+ result
74
+ end
75
+
76
+ def execute_local_activity(activity_class, *input, **args)
77
+ options = args.delete(:options) || {}
78
+ input << args unless args.empty?
79
+
80
+ execution_options = ExecutionOptions.new(activity_class, options)
81
+ activity_id = options[:activity_id] || SecureRandom.uuid
82
+ metadata = Metadata::Activity.new(
83
+ namespace: execution_options.namespace,
84
+ id: activity_id,
85
+ name: execution_options.name,
86
+ task_token: nil,
87
+ attempt: 1,
88
+ workflow_run_id: run_id,
89
+ workflow_id: workflow_id,
90
+ workflow_name: nil, # not yet used, but will be in the future
91
+ headers: execution_options.headers
92
+ )
93
+ context = LocalActivityContext.new(metadata)
94
+
95
+ activity_class.execute_in_context(context, input)
96
+ end
97
+
98
+ def execute_workflow(workflow_class, *input, **args)
99
+ raise NotImplementedError, 'not yet available for testing'
100
+ end
101
+
102
+ def execute_workflow!(workflow_class, *input, **args)
103
+ options = args.delete(:options) || {}
104
+ input << args unless args.empty?
105
+
106
+ execution = WorkflowExecution.new
107
+ workflow_id = SecureRandom.uuid
108
+ run_id = SecureRandom.uuid
109
+ execution_options = ExecutionOptions.new(workflow_class, options)
110
+ context = Temporal::Testing::LocalWorkflowContext.new(
111
+ execution, workflow_id, run_id, workflow_class.disabled_releases, execution_options.headers
112
+ )
113
+
114
+ workflow_class.execute_in_context(context, input)
115
+ end
116
+
117
+ def side_effect(&block)
118
+ block.call
119
+ end
120
+
121
+ def sleep(timeout)
122
+ ::Kernel.sleep timeout
123
+ end
124
+
125
+ def start_timer(timeout, timer_id = nil)
126
+ raise NotImplementedError, 'not yet available for testing'
127
+ end
128
+
129
+ def cancel_timer(timer_id)
130
+ raise NotImplementedError, 'not yet available for testing'
131
+ end
132
+
133
+ def complete(result = nil)
134
+ result
135
+ end
136
+
137
+ def fail(exception)
138
+ raise exception
139
+ end
140
+
141
+ def wait_for_all(*futures)
142
+ futures.each(&:wait)
143
+
144
+ return
145
+ end
146
+
147
+ def wait_for(future)
148
+ # Point of communication
149
+ Fiber.yield while !future.finished?
150
+ end
151
+
152
+ def now
153
+ Time.now
154
+ end
155
+
156
+ def on_signal(&block)
157
+ raise NotImplementedError, 'not yet available for testing'
158
+ end
159
+
160
+ def cancel_activity(activity_id)
161
+ raise NotImplementedError, 'not yet available for testing'
162
+ end
163
+
164
+ def cancel(target, cancelation_id)
165
+ raise NotImplementedError, 'not yet available for testing'
166
+ end
167
+
168
+ private
169
+
170
+ attr_reader :execution, :run_id, :workflow_id, :disabled_releases
171
+
172
+ def next_event_id
173
+ @last_event_id += 1
174
+ @last_event_id
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,121 @@
1
+ require 'securerandom'
2
+ require 'temporal/activity/async_token'
3
+ require 'temporal/workflow/execution_info'
4
+ require 'temporal/testing/workflow_execution'
5
+ require 'temporal/testing/local_workflow_context'
6
+
7
+ module Temporal
8
+ module Testing
9
+ module TemporalOverride
10
+ def start_workflow(workflow, *input, **args)
11
+ return super if Temporal::Testing.disabled?
12
+
13
+ if Temporal::Testing.local?
14
+ start_locally(workflow, *input, **args)
15
+ end
16
+ end
17
+
18
+ def fetch_workflow_execution_info(_namespace, workflow_id, run_id)
19
+ return super if Temporal::Testing.disabled?
20
+
21
+ execution = executions[[workflow_id, run_id]]
22
+
23
+ Workflow::ExecutionInfo.new(
24
+ workflow: nil,
25
+ workflow_id: workflow_id,
26
+ run_id: run_id,
27
+ start_time: nil,
28
+ close_time: nil,
29
+ status: execution.status,
30
+ history_length: nil,
31
+ ).freeze
32
+ end
33
+
34
+ def complete_activity(async_token, result = nil)
35
+ return super if Temporal::Testing.disabled?
36
+
37
+ details = Activity::AsyncToken.decode(async_token)
38
+ execution = executions[[details.workflow_id, details.run_id]]
39
+
40
+ execution.complete_activity(async_token, result)
41
+ end
42
+
43
+ def fail_activity(async_token, exception)
44
+ return super if Temporal::Testing.disabled?
45
+
46
+ details = Activity::AsyncToken.decode(async_token)
47
+ execution = executions[[details.workflow_id, details.run_id]]
48
+
49
+ execution.fail_activity(async_token, exception)
50
+ end
51
+
52
+ private
53
+
54
+ def executions
55
+ @executions ||= {}
56
+ end
57
+
58
+ def start_locally(workflow, *input, **args)
59
+ options = args.delete(:options) || {}
60
+ input << args unless args.empty?
61
+
62
+ reuse_policy = options[:workflow_id_reuse_policy] || :allow_failed
63
+ workflow_id = options[:workflow_id] || SecureRandom.uuid
64
+ run_id = SecureRandom.uuid
65
+
66
+ if !allowed?(workflow_id, reuse_policy)
67
+ raise Temporal::WorkflowExecutionAlreadyStartedFailure.new(
68
+ "Workflow execution already started for id #{workflow_id}, reuse policy #{reuse_policy}",
69
+ previous_run_id(workflow_id)
70
+ )
71
+ end
72
+
73
+ execution = WorkflowExecution.new
74
+ executions[[workflow_id, run_id]] = execution
75
+
76
+ execution_options = ExecutionOptions.new(workflow, options)
77
+ headers = execution_options.headers
78
+ context = Temporal::Testing::LocalWorkflowContext.new(
79
+ execution, workflow_id, run_id, workflow.disabled_releases, headers
80
+ )
81
+
82
+ execution.run do
83
+ workflow.execute_in_context(context, input)
84
+ end
85
+
86
+ run_id
87
+ end
88
+
89
+ def allowed?(workflow_id, reuse_policy)
90
+ disallowed_statuses = disallowed_statuses_for(reuse_policy)
91
+
92
+ # there isn't a single execution in a dissallowed status
93
+ executions.none? do |(w_id, _), execution|
94
+ w_id == workflow_id && disallowed_statuses.include?(execution.status)
95
+ end
96
+ end
97
+
98
+ def previous_run_id(workflow_id)
99
+ executions.each do |(w_id, run_id), _|
100
+ return run_id if w_id == workflow_id
101
+ end
102
+ nil
103
+ end
104
+
105
+ def disallowed_statuses_for(reuse_policy)
106
+ case reuse_policy
107
+ when :allow_failed
108
+ [Workflow::ExecutionInfo::RUNNING_STATUS, Workflow::ExecutionInfo::COMPLETED_STATUS]
109
+ when :allow
110
+ [Workflow::ExecutionInfo::RUNNING_STATUS]
111
+ when :reject
112
+ [
113
+ Workflow::ExecutionInfo::RUNNING_STATUS,
114
+ Workflow::ExecutionInfo::FAILED_STATUS,
115
+ Workflow::ExecutionInfo::COMPLETED_STATUS
116
+ ]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,44 @@
1
+ require 'temporal/testing/future_registry'
2
+
3
+ module Temporal
4
+ module Testing
5
+ class WorkflowExecution
6
+ attr_reader :status
7
+
8
+ def initialize
9
+ @status = Workflow::ExecutionInfo::RUNNING_STATUS
10
+ @futures = FutureRegistry.new
11
+ end
12
+
13
+ def run(&block)
14
+ @fiber = Fiber.new(&block)
15
+ resume
16
+ end
17
+
18
+ def resume
19
+ fiber.resume
20
+ @status = Workflow::ExecutionInfo::COMPLETED_STATUS unless fiber.alive?
21
+ rescue StandardError
22
+ @status = Workflow::ExecutionInfo::FAILED_STATUS
23
+ end
24
+
25
+ def register_future(token, future)
26
+ futures.register(token, future)
27
+ end
28
+
29
+ def complete_activity(token, result)
30
+ futures.complete(token, result)
31
+ resume
32
+ end
33
+
34
+ def fail_activity(token, exception)
35
+ futures.fail(token, exception)
36
+ resume
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :fiber, :futures
42
+ end
43
+ end
44
+ end