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,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