amit-temporalio 0.3.1-x86_64-linux-musl

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 (177) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +2 -0
  3. data/Gemfile +23 -0
  4. data/Rakefile +101 -0
  5. data/lib/temporalio/activity/complete_async_error.rb +11 -0
  6. data/lib/temporalio/activity/context.rb +116 -0
  7. data/lib/temporalio/activity/definition.rb +189 -0
  8. data/lib/temporalio/activity/info.rb +64 -0
  9. data/lib/temporalio/activity.rb +12 -0
  10. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  11. data/lib/temporalio/api/batch/v1/message.rb +31 -0
  12. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  13. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +126 -0
  14. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
  15. data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
  16. data/lib/temporalio/api/cloud/identity/v1/message.rb +41 -0
  17. data/lib/temporalio/api/cloud/namespace/v1/message.rb +42 -0
  18. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  19. data/lib/temporalio/api/cloud/operation/v1/message.rb +28 -0
  20. data/lib/temporalio/api/cloud/region/v1/message.rb +24 -0
  21. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  22. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  23. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  24. data/lib/temporalio/api/command/v1/message.rb +46 -0
  25. data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
  26. data/lib/temporalio/api/common/v1/message.rb +47 -0
  27. data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
  28. data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
  29. data/lib/temporalio/api/enums/v1/common.rb +26 -0
  30. data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
  31. data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
  32. data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
  33. data/lib/temporalio/api/enums/v1/query.rb +22 -0
  34. data/lib/temporalio/api/enums/v1/reset.rb +23 -0
  35. data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
  36. data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
  37. data/lib/temporalio/api/enums/v1/update.rb +22 -0
  38. data/lib/temporalio/api/enums/v1/workflow.rb +30 -0
  39. data/lib/temporalio/api/errordetails/v1/message.rb +42 -0
  40. data/lib/temporalio/api/export/v1/message.rb +24 -0
  41. data/lib/temporalio/api/failure/v1/message.rb +35 -0
  42. data/lib/temporalio/api/filter/v1/message.rb +27 -0
  43. data/lib/temporalio/api/history/v1/message.rb +90 -0
  44. data/lib/temporalio/api/namespace/v1/message.rb +31 -0
  45. data/lib/temporalio/api/nexus/v1/message.rb +40 -0
  46. data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
  47. data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
  48. data/lib/temporalio/api/operatorservice.rb +3 -0
  49. data/lib/temporalio/api/payload_visitor.rb +1513 -0
  50. data/lib/temporalio/api/protocol/v1/message.rb +23 -0
  51. data/lib/temporalio/api/query/v1/message.rb +27 -0
  52. data/lib/temporalio/api/replication/v1/message.rb +26 -0
  53. data/lib/temporalio/api/schedule/v1/message.rb +43 -0
  54. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +25 -0
  55. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +21 -0
  56. data/lib/temporalio/api/sdk/v1/user_metadata.rb +23 -0
  57. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +23 -0
  58. data/lib/temporalio/api/taskqueue/v1/message.rb +45 -0
  59. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  60. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  61. data/lib/temporalio/api/update/v1/message.rb +33 -0
  62. data/lib/temporalio/api/version/v1/message.rb +26 -0
  63. data/lib/temporalio/api/workflow/v1/message.rb +43 -0
  64. data/lib/temporalio/api/workflowservice/v1/request_response.rb +204 -0
  65. data/lib/temporalio/api/workflowservice/v1/service.rb +23 -0
  66. data/lib/temporalio/api/workflowservice.rb +3 -0
  67. data/lib/temporalio/api.rb +14 -0
  68. data/lib/temporalio/cancellation.rb +170 -0
  69. data/lib/temporalio/client/activity_id_reference.rb +32 -0
  70. data/lib/temporalio/client/async_activity_handle.rb +85 -0
  71. data/lib/temporalio/client/connection/cloud_service.rb +726 -0
  72. data/lib/temporalio/client/connection/operator_service.rb +201 -0
  73. data/lib/temporalio/client/connection/service.rb +42 -0
  74. data/lib/temporalio/client/connection/test_service.rb +111 -0
  75. data/lib/temporalio/client/connection/workflow_service.rb +1041 -0
  76. data/lib/temporalio/client/connection.rb +316 -0
  77. data/lib/temporalio/client/interceptor.rb +416 -0
  78. data/lib/temporalio/client/schedule.rb +967 -0
  79. data/lib/temporalio/client/schedule_handle.rb +126 -0
  80. data/lib/temporalio/client/workflow_execution.rb +100 -0
  81. data/lib/temporalio/client/workflow_execution_count.rb +36 -0
  82. data/lib/temporalio/client/workflow_execution_status.rb +18 -0
  83. data/lib/temporalio/client/workflow_handle.rb +389 -0
  84. data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
  85. data/lib/temporalio/client/workflow_update_handle.rb +65 -0
  86. data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
  87. data/lib/temporalio/client.rb +484 -0
  88. data/lib/temporalio/common_enums.rb +41 -0
  89. data/lib/temporalio/converters/data_converter.rb +99 -0
  90. data/lib/temporalio/converters/failure_converter.rb +202 -0
  91. data/lib/temporalio/converters/payload_codec.rb +26 -0
  92. data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
  93. data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
  94. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
  95. data/lib/temporalio/converters/payload_converter/composite.rb +66 -0
  96. data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
  97. data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
  98. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
  99. data/lib/temporalio/converters/payload_converter.rb +71 -0
  100. data/lib/temporalio/converters/raw_value.rb +20 -0
  101. data/lib/temporalio/converters.rb +9 -0
  102. data/lib/temporalio/error/failure.rb +219 -0
  103. data/lib/temporalio/error.rb +155 -0
  104. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  105. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  106. data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +0 -0
  107. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
  108. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +31 -0
  109. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
  110. data/lib/temporalio/internal/bridge/api/common/common.rb +26 -0
  111. data/lib/temporalio/internal/bridge/api/core_interface.rb +40 -0
  112. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
  113. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  114. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +56 -0
  115. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +57 -0
  116. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +30 -0
  117. data/lib/temporalio/internal/bridge/api.rb +3 -0
  118. data/lib/temporalio/internal/bridge/client.rb +95 -0
  119. data/lib/temporalio/internal/bridge/runtime.rb +53 -0
  120. data/lib/temporalio/internal/bridge/testing.rb +66 -0
  121. data/lib/temporalio/internal/bridge/worker.rb +85 -0
  122. data/lib/temporalio/internal/bridge.rb +36 -0
  123. data/lib/temporalio/internal/client/implementation.rb +700 -0
  124. data/lib/temporalio/internal/metric.rb +122 -0
  125. data/lib/temporalio/internal/proto_utils.rb +133 -0
  126. data/lib/temporalio/internal/worker/activity_worker.rb +376 -0
  127. data/lib/temporalio/internal/worker/multi_runner.rb +213 -0
  128. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  129. data/lib/temporalio/internal/worker/workflow_instance/context.rb +333 -0
  130. data/lib/temporalio/internal/worker/workflow_instance/details.rb +44 -0
  131. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  132. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  133. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  134. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  135. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  136. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  137. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +415 -0
  138. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  139. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  140. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +163 -0
  141. data/lib/temporalio/internal/worker/workflow_instance.rb +730 -0
  142. data/lib/temporalio/internal/worker/workflow_worker.rb +236 -0
  143. data/lib/temporalio/internal.rb +7 -0
  144. data/lib/temporalio/metric.rb +109 -0
  145. data/lib/temporalio/retry_policy.rb +74 -0
  146. data/lib/temporalio/runtime.rb +314 -0
  147. data/lib/temporalio/scoped_logger.rb +96 -0
  148. data/lib/temporalio/search_attributes.rb +343 -0
  149. data/lib/temporalio/testing/activity_environment.rb +136 -0
  150. data/lib/temporalio/testing/workflow_environment.rb +383 -0
  151. data/lib/temporalio/testing.rb +10 -0
  152. data/lib/temporalio/version.rb +5 -0
  153. data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
  154. data/lib/temporalio/worker/activity_executor/thread_pool.rb +46 -0
  155. data/lib/temporalio/worker/activity_executor.rb +55 -0
  156. data/lib/temporalio/worker/interceptor.rb +362 -0
  157. data/lib/temporalio/worker/thread_pool.rb +237 -0
  158. data/lib/temporalio/worker/tuner.rb +189 -0
  159. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +230 -0
  160. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  161. data/lib/temporalio/worker/workflow_replayer.rb +343 -0
  162. data/lib/temporalio/worker.rb +569 -0
  163. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  164. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  165. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  166. data/lib/temporalio/workflow/definition.rb +566 -0
  167. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  168. data/lib/temporalio/workflow/future.rb +151 -0
  169. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  170. data/lib/temporalio/workflow/info.rb +82 -0
  171. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  172. data/lib/temporalio/workflow/update_info.rb +20 -0
  173. data/lib/temporalio/workflow.rb +529 -0
  174. data/lib/temporalio/workflow_history.rb +47 -0
  175. data/lib/temporalio.rb +11 -0
  176. data/temporalio.gemspec +28 -0
  177. metadata +238 -0
@@ -0,0 +1,566 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/workflow'
4
+ require 'temporalio/workflow/handler_unfinished_policy'
5
+
6
+ module Temporalio
7
+ module Workflow
8
+ # Base class for all workflows.
9
+ #
10
+ # Workflows are instances of this class and must implement {execute}. Inside the workflow code, class methods on
11
+ # {Workflow} can be used.
12
+ #
13
+ # By default, the workflow is named as its unqualified class name. This can be customized with {workflow_name}.
14
+ class Definition
15
+ class << self
16
+ protected
17
+
18
+ # Customize the workflow name. By default the workflow is named the unqualified class name of the class provided
19
+ # to the worker.
20
+ #
21
+ # @param workflow_name [String, Symbol] Name to use.
22
+ def workflow_name(workflow_name)
23
+ if !workflow_name.is_a?(Symbol) && !workflow_name.is_a?(String)
24
+ raise ArgumentError,
25
+ 'Workflow name must be a symbol or string'
26
+ end
27
+
28
+ @workflow_name = workflow_name.to_s
29
+ end
30
+
31
+ # Set a workflow as dynamic. Dynamic workflows do not have names and handle any workflow that is not otherwise
32
+ # registered. A worker can only have one dynamic workflow. It is often useful to use {workflow_raw_args} with
33
+ # this.
34
+ #
35
+ # @param value [Boolean] Whether the workflow is dynamic.
36
+ def workflow_dynamic(value = true) # rubocop:disable Style/OptionalBooleanParameter
37
+ @workflow_dynamic = value
38
+ end
39
+
40
+ # Have workflow arguments delivered to `execute` (and `initialize` if {workflow_init} in use) as
41
+ # {Converters::RawValue}s. These are wrappers for the raw payloads that have not been converted to types (but
42
+ # they have been decoded by the codec if present). They can be converted with {Workflow.payload_converter}.
43
+ #
44
+ # @param value [Boolean] Whether the workflow accepts raw arguments.
45
+ def workflow_raw_args(value = true) # rubocop:disable Style/OptionalBooleanParameter
46
+ @workflow_raw_args = value
47
+ end
48
+
49
+ # Configure workflow failure exception types. This sets the types of exceptions that, if a
50
+ # workflow-thrown exception extends, will cause the workflow/update to fail instead of suspending the workflow
51
+ # via task failure. These are applied in addition to the worker option. If {::Exception} is set, it effectively
52
+ # will fail a workflow/update in all user exception cases.
53
+ #
54
+ # @param types [Array<Class<Exception>>] Exception types to turn into workflow failures.
55
+ def workflow_failure_exception_type(*types)
56
+ types.each do |t|
57
+ raise ArgumentError, 'All types must classes inheriting Exception' unless t.is_a?(Class) && t < Exception
58
+ end
59
+ @workflow_failure_exception_types ||= []
60
+ @workflow_failure_exception_types.concat(types)
61
+ end
62
+
63
+ # Expose an attribute as a method and as a query. A `workflow_query_attr_reader :foo` is the equivalent of:
64
+ # ```
65
+ # workflow_query
66
+ # def foo
67
+ # @foo
68
+ # end
69
+ # ```
70
+ # This means it is a superset of `attr_reader`` and will not work if also using `attr_reader` or
71
+ # `attr_accessor`. If a writer is needed alongside this, use `attr_writer`.
72
+ #
73
+ # @param attr_names [Array<Symbol>] Attributes to expose.
74
+ def workflow_query_attr_reader(*attr_names)
75
+ @workflow_queries ||= {}
76
+ attr_names.each do |attr_name|
77
+ raise 'Expected attr to be a symbol' unless attr_name.is_a?(Symbol)
78
+
79
+ if method_defined?(attr_name, false)
80
+ raise 'Method already defined for this attr name. ' \
81
+ 'Note that a workflow_query_attr_reader includes attr_reader behavior. ' \
82
+ 'If you also want a writer for this attribute, use a separate attr_writer.'
83
+ end
84
+
85
+ # Just run this as if done manually
86
+ workflow_query
87
+ define_method(attr_name) { instance_variable_get("@#{attr_name}") }
88
+ end
89
+ end
90
+
91
+ # Mark an `initialize` as needing the workflow start arguments. Otherwise, `initialize` must accept no required
92
+ # arguments. This must be placed above the `initialize` method or it will fail.
93
+ #
94
+ # @param value [Boolean] Whether the start parameters will be passed to `initialize`.
95
+ def workflow_init(value = true) # rubocop:disable Style/OptionalBooleanParameter
96
+ self.pending_handler_details = { type: :init, value: }
97
+ end
98
+
99
+ # Mark the next method as a workflow signal with a default name as the name of the method. Signals cannot return
100
+ # values.
101
+ #
102
+ # @param name [String, Symbol, nil] Override the default name.
103
+ # @param dynamic [Boolean] If true, make the signal dynamic. This means it receives all other signals without
104
+ # handlers. This cannot have a name override since it is nameless. The first parameter will be the name. Often
105
+ # it is useful to have the second parameter be `*args` and `raw_args` be true.
106
+ # @param raw_args [Boolean] If true, does not convert arguments, but instead provides each argument as
107
+ # {Converters::RawValue} which is a raw payload wrapper, convertible with {Workflow.payload_converter}.
108
+ # @param unfinished_policy [HandlerUnfinishedPolicy] How to treat unfinished handlers if they are still running
109
+ # when the workflow ends. The default warns, but this can be disabled.
110
+ def workflow_signal(
111
+ name: nil,
112
+ dynamic: false,
113
+ raw_args: false,
114
+ unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON
115
+ )
116
+ raise 'Cannot provide name if dynamic is true' if name && dynamic
117
+
118
+ self.pending_handler_details = { type: :signal, name:, dynamic:, raw_args:, unfinished_policy: }
119
+ end
120
+
121
+ # Mark the next method as a workflow query with a default name as the name of the method. Queries can not have
122
+ # any side effects, meaning they should never mutate state or try to wait on anything.
123
+ #
124
+ # @param name [String, Symbol, nil] Override the default name.
125
+ # @param dynamic [Boolean] If true, make the query dynamic. This means it receives all other queries without
126
+ # handlers. This cannot have a name override since it is nameless. The first parameter will be the name. Often
127
+ # it is useful to have the second parameter be `*args` and `raw_args` be true.
128
+ # @param raw_args [Boolean] If true, does not convert arguments, but instead provides each argument as
129
+ # {Converters::RawValue} which is a raw payload wrapper, convertible with {Workflow.payload_converter}.
130
+ def workflow_query(
131
+ name: nil,
132
+ dynamic: false,
133
+ raw_args: false
134
+ )
135
+ raise 'Cannot provide name if dynamic is true' if name && dynamic
136
+
137
+ self.pending_handler_details = { type: :query, name:, dynamic:, raw_args: }
138
+ end
139
+
140
+ # Mark the next method as a workflow update with a default name as the name of the method. Updates can return
141
+ # values. Separate validation methods can be provided via {workflow_update_validator}.
142
+ #
143
+ # @param name [String, Symbol, nil] Override the default name.
144
+ # @param dynamic [Boolean] If true, make the update dynamic. This means it receives all other updates without
145
+ # handlers. This cannot have a name override since it is nameless. The first parameter will be the name. Often
146
+ # it is useful to have the second parameter be `*args` and `raw_args` be true.
147
+ # @param raw_args [Boolean] If true, does not convert arguments, but instead provides each argument as
148
+ # {Converters::RawValue} which is a raw payload wrapper, convertible with {Workflow.payload_converter}.
149
+ # @param unfinished_policy [HandlerUnfinishedPolicy] How to treat unfinished handlers if they are still running
150
+ # when the workflow ends. The default warns, but this can be disabled.
151
+ def workflow_update(
152
+ name: nil,
153
+ dynamic: false,
154
+ raw_args: false,
155
+ unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON
156
+ )
157
+ raise 'Cannot provide name if dynamic is true' if name && dynamic
158
+
159
+ self.pending_handler_details = { type: :update, name:, dynamic:, raw_args:, unfinished_policy: }
160
+ end
161
+
162
+ # Mark the next method as a workflow update validator to the given update method. The validator is expected to
163
+ # have the exact same parameter signature. It will run before an update and if it raises an exception, the
164
+ # update will be rejected, possibly before even reaching history. Validators cannot have any side effects or do
165
+ # any waiting, and they do not return values.
166
+ #
167
+ # @param update_method [Symbol] Name of the update method.
168
+ def workflow_update_validator(update_method)
169
+ self.pending_handler_details = { type: :update_validator, update_method: }
170
+ end
171
+
172
+ private
173
+
174
+ attr_reader :pending_handler_details
175
+
176
+ def pending_handler_details=(value)
177
+ if value.nil?
178
+ @pending_handler_details = value
179
+ return
180
+ elsif @pending_handler_details
181
+ raise "Previous #{@pending_handler_details[:type]} handler was not put on method before this handler"
182
+ end
183
+
184
+ @pending_handler_details = value
185
+ end
186
+ end
187
+
188
+ # @!visibility private
189
+ def self.method_added(method_name)
190
+ super
191
+
192
+ # Nothing to do if there are no pending handler details
193
+ handler = pending_handler_details
194
+ return unless handler
195
+
196
+ # Reset details
197
+ self.pending_handler_details = nil
198
+
199
+ # Initialize class variables if not done already
200
+ @workflow_signals ||= {}
201
+ @workflow_queries ||= {}
202
+ @workflow_updates ||= {}
203
+ @workflow_update_validators ||= {}
204
+ @defined_methods ||= []
205
+
206
+ defn, hash, other_hashes =
207
+ case handler[:type]
208
+ when :init
209
+ raise "workflow_init was applied to #{method_name} instead of initialize" if method_name != :initialize
210
+
211
+ @workflow_init = handler[:value]
212
+ return
213
+ when :update_validator
214
+ other = @workflow_update_validators[handler[:update_method]]
215
+ if other && (other[:method_name] != method_name || other[:update_method] != handler[:update_method])
216
+ raise "Workflow update validator on #{method_name} for #{handler[:update_method]} defined separately " \
217
+ "on #{other[:method_name]} for #{other[:update_method]}"
218
+ end
219
+
220
+ # Just store this, we'll apply validators to updates at definition
221
+ # building time
222
+ @workflow_update_validators[handler[:update_method]] = { method_name:, **handler }
223
+ return
224
+ when :signal
225
+ [Signal.new(
226
+ name: handler[:dynamic] ? nil : (handler[:name] || method_name).to_s,
227
+ to_invoke: method_name,
228
+ raw_args: handler[:raw_args],
229
+ unfinished_policy: handler[:unfinished_policy]
230
+ ), @workflow_signals, [@workflow_queries, @workflow_updates]]
231
+ when :query
232
+ [Query.new(
233
+ name: handler[:dynamic] ? nil : (handler[:name] || method_name).to_s,
234
+ to_invoke: method_name,
235
+ raw_args: handler[:raw_args]
236
+ ), @workflow_queries, [@workflow_signals, @workflow_updates]]
237
+ when :update
238
+ [Update.new(
239
+ name: handler[:dynamic] ? nil : (handler[:name] || method_name).to_s,
240
+ to_invoke: method_name,
241
+ raw_args: handler[:raw_args],
242
+ unfinished_policy: handler[:unfinished_policy]
243
+ ), @workflow_updates, [@workflow_signals, @workflow_queries]]
244
+ else
245
+ raise "Unrecognized handler type #{handler[:type]}"
246
+ end
247
+
248
+ # We only allow dupes with the same method name (override/redefine)
249
+ # TODO(cretz): Should we also check that everything else is the same?
250
+ other = hash[defn.name]
251
+ if other && other.to_invoke != method_name
252
+ raise "Workflow #{handler[:type].name} #{defn.name || '<dynamic>'} defined on " \
253
+ "different methods #{other.to_invoke} and #{method_name}"
254
+ elsif defn.name && other_hashes.any? { |h| h.include?(defn.name) }
255
+ raise "Workflow signal #{defn.name} already defined as a different handler type"
256
+ end
257
+ hash[defn.name] = defn
258
+
259
+ # Define class method for referencing the definition only if non-dynamic
260
+ return unless defn.name
261
+
262
+ define_singleton_method(method_name) { defn }
263
+ @defined_methods.push(method_name)
264
+ end
265
+
266
+ # @!visibility private
267
+ def self.singleton_method_added(method_name)
268
+ super
269
+ # We need to ensure class methods are not added after we have defined a method
270
+ return unless @defined_methods&.include?(method_name)
271
+
272
+ raise 'Attempting to override Temporal-defined class definition method'
273
+ end
274
+
275
+ # @!visibility private
276
+ def self._workflow_definition
277
+ @workflow_definition ||= _build_workflow_definition
278
+ end
279
+
280
+ # @!visibility private
281
+ def self._workflow_type_from_workflow_parameter(workflow)
282
+ case workflow
283
+ when Class
284
+ unless workflow < Definition
285
+ raise ArgumentError, "Class '#{workflow}' does not extend Temporalio::Workflow::Definition"
286
+ end
287
+
288
+ info = Info.from_class(workflow)
289
+ info.name || raise(ArgumentError, 'Cannot pass dynamic workflow to start')
290
+ when Info
291
+ workflow.name || raise(ArgumentError, 'Cannot pass dynamic workflow to start')
292
+ when String, Symbol
293
+ workflow.to_s
294
+ else
295
+ raise ArgumentError, 'Workflow is not a workflow class or string/symbol'
296
+ end
297
+ end
298
+
299
+ # @!visibility private
300
+ def self._build_workflow_definition
301
+ # Make sure there isn't dangling pending handler details
302
+ if pending_handler_details
303
+ raise "Leftover #{pending_handler_details&.[](:type)} handler not applied to a method"
304
+ end
305
+
306
+ # Apply all update validators before merging with super
307
+ updates = @workflow_updates&.dup || {}
308
+ @workflow_update_validators&.each_value do |validator|
309
+ update = updates.values.find { |u| u.to_invoke == validator[:update_method] }
310
+ unless update
311
+ raise "Unable to find update #{validator[:update_method]} pointed to by " \
312
+ "validator on #{validator[:method_name]}"
313
+ end
314
+ if instance_method(validator[:method_name])&.parameters !=
315
+ instance_method(validator[:update_method])&.parameters
316
+ raise "Validator on #{validator[:method_name]} does not have " \
317
+ "exact parameter signature of #{validator[:update_method]}"
318
+ end
319
+
320
+ updates[update.name] = update._with_validator_to_invoke(validator[:method_name])
321
+ end
322
+
323
+ # If there is a superclass, apply some values and check others
324
+ override_name = @workflow_name
325
+ dynamic = @workflow_dynamic
326
+ init = @workflow_init
327
+ raw_args = @workflow_raw_args
328
+ signals = @workflow_signals || {}
329
+ queries = @workflow_queries || {}
330
+ if superclass && superclass != Temporalio::Workflow::Definition
331
+ # @type var super_info: Temporalio::Workflow::Definition::Info
332
+ super_info = superclass._workflow_definition # steep:ignore
333
+
334
+ # Override values if not set here
335
+ override_name = super_info.override_name if override_name.nil?
336
+ dynamic = super_info.dynamic if dynamic.nil?
337
+ init = super_info.init if init.nil?
338
+ raw_args = super_info.raw_args if raw_args.nil?
339
+
340
+ # Make sure handlers on the same method at least have the same name
341
+ # TODO(cretz): Need to validate any other handler override details?
342
+ # Probably not because we only care that caller-needed values remain
343
+ # unchanged (method and name), implementer-needed values can be
344
+ # overridden/changed.
345
+ self_handlers = signals.values + queries.values + updates.values
346
+ super_handlers = super_info.signals.values + super_info.queries.values + super_info.updates.values
347
+ super_handlers.each do |super_handler|
348
+ self_handler = self_handlers.find { |h| h.to_invoke == super_handler.to_invoke }
349
+ next unless self_handler
350
+
351
+ if super_handler.class != self_handler.class
352
+ raise "Superclass handler on #{self_handler.to_invoke} is a #{super_handler.class} " \
353
+ "but current class expects #{self_handler.class}"
354
+ end
355
+ if super_handler.name != self_handler.name
356
+ raise "Superclass handler on #{self_handler.to_invoke} has name #{super_handler.name} " \
357
+ "but current class expects #{self_handler.name}"
358
+ end
359
+ end
360
+
361
+ # Merge handlers. We will merge such that handlers defined here
362
+ # override ones from superclass by _name_ (not method to invoke).
363
+ signals = super_info.signals.merge(signals)
364
+ queries = super_info.queries.merge(queries)
365
+ updates = super_info.updates.merge(updates)
366
+ end
367
+
368
+ # If init is true, validate initialize and execute signatures are identical
369
+ if init && instance_method(:initialize)&.parameters&.size != instance_method(:execute)&.parameters&.size
370
+ raise 'workflow_init present, so parameter count of initialize and execute must be the same'
371
+ end
372
+
373
+ raise 'Workflow cannot be given a name and be dynamic' if dynamic && override_name
374
+
375
+ Info.new(
376
+ workflow_class: self,
377
+ override_name:,
378
+ dynamic: dynamic || false,
379
+ init: init || false,
380
+ raw_args: raw_args || false,
381
+ failure_exception_types: @workflow_failure_exception_types || [],
382
+ signals:,
383
+ queries:,
384
+ updates:
385
+ )
386
+ end
387
+
388
+ # Execute the workflow. This is the primary workflow method. The workflow is completed when this method completes.
389
+ # This must be implemented by all workflows.
390
+ def execute(*args)
391
+ raise NotImplementedError, 'Workflow did not implement "execute"'
392
+ end
393
+
394
+ # Information about the workflow definition. This is usually not used directly.
395
+ class Info
396
+ attr_reader :workflow_class, :override_name, :dynamic, :init, :raw_args,
397
+ :failure_exception_types, :signals, :queries, :updates
398
+
399
+ # Derive the workflow definition info from the class.
400
+ #
401
+ # @param workflow_class [Class<Definition>] Workflow class.
402
+ # @return [Info] Built info.
403
+ def self.from_class(workflow_class)
404
+ unless workflow_class.is_a?(Class) && workflow_class < Definition
405
+ raise "Workflow '#{workflow_class}' must be a class and must extend Temporalio::Workflow::Definition"
406
+ end
407
+
408
+ workflow_class._workflow_definition
409
+ end
410
+
411
+ # Create a definition info. This should usually not be used directly, but instead a class that extends
412
+ # {Workflow::Definition} should be used.
413
+ def initialize(
414
+ workflow_class:,
415
+ override_name: nil,
416
+ dynamic: false,
417
+ init: false,
418
+ raw_args: false,
419
+ failure_exception_types: [],
420
+ signals: {},
421
+ queries: {},
422
+ updates: {}
423
+ )
424
+ @workflow_class = workflow_class
425
+ @override_name = override_name
426
+ @dynamic = dynamic
427
+ @init = init
428
+ @raw_args = raw_args
429
+ @failure_exception_types = failure_exception_types.dup.freeze
430
+ @signals = signals.dup.freeze
431
+ @queries = queries.dup.freeze
432
+ @updates = updates.dup.freeze
433
+ end
434
+
435
+ # @return [String] Workflow name.
436
+ def name
437
+ dynamic ? nil : (override_name || workflow_class.name.to_s.split('::').last)
438
+ end
439
+ end
440
+
441
+ # A signal definition. This is usually built as a result of a {Definition.workflow_signal} method, but can be
442
+ # manually created to set at runtime on {Workflow.signal_handlers}.
443
+ class Signal
444
+ attr_reader :name, :to_invoke, :raw_args, :unfinished_policy
445
+
446
+ # @!visibility private
447
+ def self._name_from_parameter(signal)
448
+ case signal
449
+ when Workflow::Definition::Signal
450
+ signal.name || raise(ArgumentError, 'Cannot call dynamic signal directly')
451
+ when String, Symbol
452
+ signal.to_s
453
+ else
454
+ raise ArgumentError, 'Signal is not a definition or string/symbol'
455
+ end
456
+ end
457
+
458
+ # Create a signal definition manually. See {Definition.workflow_signal} for more details on some of the
459
+ # parameters.
460
+ #
461
+ # @param name [String, nil] Name or nil if dynamic.
462
+ # @param to_invoke [Symbol, Proc] Method name or proc to invoke.
463
+ # @param raw_args [Boolean] Whether the parameters should be raw values.
464
+ # @param unfinished_policy [HandlerUnfinishedPolicy] How the workflow reacts when this handler is still running
465
+ # on workflow completion.
466
+ def initialize(
467
+ name:,
468
+ to_invoke:,
469
+ raw_args: false,
470
+ unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON
471
+ )
472
+ @name = name
473
+ @to_invoke = to_invoke
474
+ @raw_args = raw_args
475
+ @unfinished_policy = unfinished_policy
476
+ end
477
+ end
478
+
479
+ # A query definition. This is usually built as a result of a {Definition.workflow_query} method, but can be
480
+ # manually created to set at runtime on {Workflow.query_handlers}.
481
+ class Query
482
+ attr_reader :name, :to_invoke, :raw_args
483
+
484
+ # @!visibility private
485
+ def self._name_from_parameter(query)
486
+ case query
487
+ when Workflow::Definition::Query
488
+ query.name || raise(ArgumentError, 'Cannot call dynamic query directly')
489
+ when String, Symbol
490
+ query.to_s
491
+ else
492
+ raise ArgumentError, 'Query is not a definition or string/symbol'
493
+ end
494
+ end
495
+
496
+ # Create a query definition manually. See {Definition.workflow_query} for more details on some of the
497
+ # parameters.
498
+ #
499
+ # @param name [String, nil] Name or nil if dynamic.
500
+ # @param to_invoke [Symbol, Proc] Method name or proc to invoke.
501
+ # @param raw_args [Boolean] Whether the parameters should be raw values.
502
+ def initialize(
503
+ name:,
504
+ to_invoke:,
505
+ raw_args: false
506
+ )
507
+ @name = name
508
+ @to_invoke = to_invoke
509
+ @raw_args = raw_args
510
+ end
511
+ end
512
+
513
+ # An update definition. This is usually built as a result of a {Definition.workflow_update} method, but can be
514
+ # manually created to set at runtime on {Workflow.update_handlers}.
515
+ class Update
516
+ attr_reader :name, :to_invoke, :raw_args, :unfinished_policy, :validator_to_invoke
517
+
518
+ # @!visibility private
519
+ def self._name_from_parameter(update)
520
+ case update
521
+ when Workflow::Definition::Update
522
+ update.name || raise(ArgumentError, 'Cannot call dynamic update directly')
523
+ when String, Symbol
524
+ update.to_s
525
+ else
526
+ raise ArgumentError, 'Update is not a definition or string/symbol'
527
+ end
528
+ end
529
+
530
+ # Create an update definition manually. See {Definition.workflow_update} for more details on some of the
531
+ # parameters.
532
+ #
533
+ # @param name [String, nil] Name or nil if dynamic.
534
+ # @param to_invoke [Symbol, Proc] Method name or proc to invoke.
535
+ # @param raw_args [Boolean] Whether the parameters should be raw values.
536
+ # @param unfinished_policy [HandlerUnfinishedPolicy] How the workflow reacts when this handler is still running
537
+ # on workflow completion.
538
+ # @param validator_to_invoke [Symbol, Proc, nil] Method name or proc validator to invoke.
539
+ def initialize(
540
+ name:,
541
+ to_invoke:,
542
+ raw_args: false,
543
+ unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON,
544
+ validator_to_invoke: nil
545
+ )
546
+ @name = name
547
+ @to_invoke = to_invoke
548
+ @raw_args = raw_args
549
+ @unfinished_policy = unfinished_policy
550
+ @validator_to_invoke = validator_to_invoke
551
+ end
552
+
553
+ # @!visibility private
554
+ def _with_validator_to_invoke(validator_to_invoke)
555
+ Update.new(
556
+ name:,
557
+ to_invoke:,
558
+ raw_args:,
559
+ unfinished_policy:,
560
+ validator_to_invoke:
561
+ )
562
+ end
563
+ end
564
+ end
565
+ end
566
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/workflow'
4
+
5
+ module Temporalio
6
+ module Workflow
7
+ # Handle for interacting with an external workflow.
8
+ #
9
+ # This is created via {Workflow.external_workflow_handle}, it is never instantiated directly.
10
+ class ExternalWorkflowHandle
11
+ # @!visibility private
12
+ def initialize
13
+ raise NotImplementedError, 'Cannot instantiate an external handle directly'
14
+ end
15
+
16
+ # @return [String] ID for the workflow.
17
+ def id
18
+ raise NotImplementedError
19
+ end
20
+
21
+ # @return [String, nil] Run ID for the workflow.
22
+ def run_id
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # Signal the external workflow.
27
+ #
28
+ # @param signal [Workflow::Definition::Signal, Symbol, String] Signal definition or name.
29
+ # @param args [Array<Object>] Signal args.
30
+ # @param cancellation [Cancellation] Cancellation for canceling the signalling.
31
+ def signal(signal, *args, cancellation: Workflow.cancellation)
32
+ raise NotImplementedError
33
+ end
34
+
35
+ # Cancel the external workflow.
36
+ def cancel
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+ end
41
+ end