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