clara-temporalio 0.4.3

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