tasker-rb 0.1.1
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.
- checksums.yaml +7 -0
- data/DEVELOPMENT.md +548 -0
- data/README.md +87 -0
- data/ext/tasker_core/Cargo.lock +4720 -0
- data/ext/tasker_core/Cargo.toml +76 -0
- data/ext/tasker_core/extconf.rb +38 -0
- data/ext/tasker_core/src/CLAUDE.md +7 -0
- data/ext/tasker_core/src/bootstrap.rs +320 -0
- data/ext/tasker_core/src/bridge.rs +400 -0
- data/ext/tasker_core/src/client_ffi.rs +173 -0
- data/ext/tasker_core/src/conversions.rs +131 -0
- data/ext/tasker_core/src/diagnostics.rs +57 -0
- data/ext/tasker_core/src/event_handler.rs +179 -0
- data/ext/tasker_core/src/event_publisher_ffi.rs +239 -0
- data/ext/tasker_core/src/ffi_logging.rs +245 -0
- data/ext/tasker_core/src/global_event_system.rs +16 -0
- data/ext/tasker_core/src/in_process_event_ffi.rs +319 -0
- data/ext/tasker_core/src/lib.rs +41 -0
- data/ext/tasker_core/src/observability_ffi.rs +339 -0
- data/lib/tasker_core/batch_processing/batch_aggregation_scenario.rb +85 -0
- data/lib/tasker_core/batch_processing/batch_worker_context.rb +238 -0
- data/lib/tasker_core/bootstrap.rb +394 -0
- data/lib/tasker_core/domain_events/base_publisher.rb +220 -0
- data/lib/tasker_core/domain_events/base_subscriber.rb +178 -0
- data/lib/tasker_core/domain_events/publisher_registry.rb +253 -0
- data/lib/tasker_core/domain_events/subscriber_registry.rb +152 -0
- data/lib/tasker_core/domain_events.rb +43 -0
- data/lib/tasker_core/errors/CLAUDE.md +7 -0
- data/lib/tasker_core/errors/common.rb +305 -0
- data/lib/tasker_core/errors/error_classifier.rb +61 -0
- data/lib/tasker_core/errors.rb +4 -0
- data/lib/tasker_core/event_bridge.rb +330 -0
- data/lib/tasker_core/handlers.rb +159 -0
- data/lib/tasker_core/internal.rb +31 -0
- data/lib/tasker_core/logger.rb +234 -0
- data/lib/tasker_core/models.rb +337 -0
- data/lib/tasker_core/observability/types.rb +158 -0
- data/lib/tasker_core/observability.rb +292 -0
- data/lib/tasker_core/registry/handler_registry.rb +453 -0
- data/lib/tasker_core/registry/resolver_chain.rb +258 -0
- data/lib/tasker_core/registry/resolvers/base_resolver.rb +90 -0
- data/lib/tasker_core/registry/resolvers/class_constant_resolver.rb +156 -0
- data/lib/tasker_core/registry/resolvers/explicit_mapping_resolver.rb +146 -0
- data/lib/tasker_core/registry/resolvers/method_dispatch_wrapper.rb +144 -0
- data/lib/tasker_core/registry/resolvers/registry_resolver.rb +229 -0
- data/lib/tasker_core/registry/resolvers.rb +42 -0
- data/lib/tasker_core/registry.rb +12 -0
- data/lib/tasker_core/step_handler/api.rb +48 -0
- data/lib/tasker_core/step_handler/base.rb +354 -0
- data/lib/tasker_core/step_handler/batchable.rb +50 -0
- data/lib/tasker_core/step_handler/decision.rb +53 -0
- data/lib/tasker_core/step_handler/mixins/api.rb +452 -0
- data/lib/tasker_core/step_handler/mixins/batchable.rb +465 -0
- data/lib/tasker_core/step_handler/mixins/decision.rb +252 -0
- data/lib/tasker_core/step_handler/mixins.rb +66 -0
- data/lib/tasker_core/subscriber.rb +212 -0
- data/lib/tasker_core/task_handler/base.rb +254 -0
- data/lib/tasker_core/tasker_rb.so +0 -0
- data/lib/tasker_core/template_discovery.rb +181 -0
- data/lib/tasker_core/tracing.rb +166 -0
- data/lib/tasker_core/types/batch_processing_outcome.rb +301 -0
- data/lib/tasker_core/types/client_types.rb +145 -0
- data/lib/tasker_core/types/decision_point_outcome.rb +177 -0
- data/lib/tasker_core/types/error_types.rb +72 -0
- data/lib/tasker_core/types/simple_message.rb +151 -0
- data/lib/tasker_core/types/step_context.rb +328 -0
- data/lib/tasker_core/types/step_handler_call_result.rb +307 -0
- data/lib/tasker_core/types/step_message.rb +112 -0
- data/lib/tasker_core/types/step_types.rb +207 -0
- data/lib/tasker_core/types/task_template.rb +240 -0
- data/lib/tasker_core/types/task_types.rb +148 -0
- data/lib/tasker_core/types.rb +132 -0
- data/lib/tasker_core/version.rb +13 -0
- data/lib/tasker_core/worker/CLAUDE.md +7 -0
- data/lib/tasker_core/worker/event_poller.rb +224 -0
- data/lib/tasker_core/worker/in_process_domain_event_poller.rb +271 -0
- data/lib/tasker_core.rb +160 -0
- metadata +322 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
# require_relative '../types' # Temporarily disabled due to dry-types version issues
|
|
7
|
+
|
|
8
|
+
module TaskerCore
|
|
9
|
+
module StepHandler
|
|
10
|
+
class Base
|
|
11
|
+
attr_reader :config, :logger, :rust_integration, :orchestration_system
|
|
12
|
+
|
|
13
|
+
def initialize(config: {}, logger: nil)
|
|
14
|
+
@config = config || {}
|
|
15
|
+
@logger = logger || TaskerCore::Logger.instance
|
|
16
|
+
orchestration_manager = TaskerCore::Internal::OrchestrationManager.instance
|
|
17
|
+
@orchestration_system = orchestration_manager.orchestration_system
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# ========================================================================
|
|
21
|
+
# MAIN STEP EXECUTION INTERFACE (implemented by subclasses)
|
|
22
|
+
# ========================================================================
|
|
23
|
+
|
|
24
|
+
# Main business logic method - cross-language standard signature
|
|
25
|
+
#
|
|
26
|
+
# Subclasses implement the actual business logic in this method.
|
|
27
|
+
# The context provides unified access to all step execution data.
|
|
28
|
+
#
|
|
29
|
+
# @param context [TaskerCore::Types::StepContext] Unified step execution context
|
|
30
|
+
# @return [TaskerCore::Types::StepHandlerCallResult] Step execution result
|
|
31
|
+
#
|
|
32
|
+
# @example Basic handler
|
|
33
|
+
# def call(context)
|
|
34
|
+
# even_number = context.get_task_field('even_number')
|
|
35
|
+
# result = even_number * 2
|
|
36
|
+
# success(result: result)
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# @example Handler with dependencies
|
|
40
|
+
# def call(context)
|
|
41
|
+
# prev_result = context.get_dependency_result('step_1')
|
|
42
|
+
# success(result: prev_result + 1)
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# @example Accessing context fields
|
|
46
|
+
# def call(context)
|
|
47
|
+
# # Cross-language standard fields
|
|
48
|
+
# context.task_uuid # Task UUID
|
|
49
|
+
# context.step_uuid # Step UUID
|
|
50
|
+
# context.input_data # Step inputs
|
|
51
|
+
# context.step_config # Handler configuration
|
|
52
|
+
# context.retry_count # Current retry attempt
|
|
53
|
+
# context.max_retries # Maximum retries allowed
|
|
54
|
+
#
|
|
55
|
+
# # Ruby-specific accessors (backward compat)
|
|
56
|
+
# context.task # TaskWrapper
|
|
57
|
+
# context.workflow_step # WorkflowStepWrapper
|
|
58
|
+
# end
|
|
59
|
+
def call(context)
|
|
60
|
+
raise NotImplementedError, 'Subclasses must implement #call(context)'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# ========================================================================
|
|
64
|
+
# CONFIGURATION AND METADATA
|
|
65
|
+
# ========================================================================
|
|
66
|
+
|
|
67
|
+
# Validate step handler configuration
|
|
68
|
+
# @param config_hash [Hash] Configuration to validate
|
|
69
|
+
# @return [Boolean] Whether configuration is valid
|
|
70
|
+
def validate_config(config_hash)
|
|
71
|
+
# Basic validation - subclasses can override for specific requirements
|
|
72
|
+
config_hash.is_a?(Hash)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get handler name for registration and logging
|
|
76
|
+
# @return [String] Handler name
|
|
77
|
+
def handler_name
|
|
78
|
+
class_name = self.class.name.split('::').last
|
|
79
|
+
# Convert CamelCase to snake_case (like Rails underscore method)
|
|
80
|
+
class_name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get handler metadata for monitoring and introspection
|
|
84
|
+
# @return [Hash] Handler metadata
|
|
85
|
+
def metadata
|
|
86
|
+
{
|
|
87
|
+
handler_name: handler_name,
|
|
88
|
+
handler_class: self.class.name,
|
|
89
|
+
version: begin
|
|
90
|
+
self.class.const_get(:VERSION)
|
|
91
|
+
rescue StandardError
|
|
92
|
+
'1.0.0'
|
|
93
|
+
end,
|
|
94
|
+
capabilities: capabilities,
|
|
95
|
+
config_schema: config_schema,
|
|
96
|
+
ruby_version: RUBY_VERSION,
|
|
97
|
+
created_at: Time.now.iso8601
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get handler capabilities
|
|
102
|
+
# @return [Array<String>] List of capabilities
|
|
103
|
+
def capabilities
|
|
104
|
+
caps = ['process']
|
|
105
|
+
caps << 'process_results' if respond_to?(:process_results, true)
|
|
106
|
+
caps << 'async' if respond_to?(:process_async, true)
|
|
107
|
+
caps << 'streaming' if respond_to?(:process_stream, true)
|
|
108
|
+
caps
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get configuration schema for validation
|
|
112
|
+
# @return [Hash] JSON schema describing expected configuration
|
|
113
|
+
def config_schema
|
|
114
|
+
{
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
timeout: { type: 'integer', minimum: 1, default: 300 },
|
|
118
|
+
retries: { type: 'integer', minimum: 0, default: 3 },
|
|
119
|
+
log_level: { type: 'string', enum: %w[debug info warn error], default: 'info' }
|
|
120
|
+
},
|
|
121
|
+
additionalProperties: true
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# ========================================================================
|
|
126
|
+
# RESULT HELPER METHODS
|
|
127
|
+
# ========================================================================
|
|
128
|
+
|
|
129
|
+
# Return a standardized success result from a step handler
|
|
130
|
+
#
|
|
131
|
+
# This wraps TaskerCore::Types::StepHandlerCallResult.success() to provide
|
|
132
|
+
# a convenient API for handlers to return properly structured results.
|
|
133
|
+
#
|
|
134
|
+
# @param result [Object] The result data (alias: result_data)
|
|
135
|
+
# @param result_data [Object] Alternative name for result parameter
|
|
136
|
+
# @param metadata [Hash] Optional metadata for observability
|
|
137
|
+
# @return [TaskerCore::Types::StepHandlerCallResult::Success] Success result
|
|
138
|
+
#
|
|
139
|
+
# @example Basic usage
|
|
140
|
+
# success(result: { order_id: "123", total: 100.00 })
|
|
141
|
+
#
|
|
142
|
+
# @example With metadata
|
|
143
|
+
# success(
|
|
144
|
+
# result: { validated: true },
|
|
145
|
+
# metadata: { processing_time_ms: 125 }
|
|
146
|
+
# )
|
|
147
|
+
#
|
|
148
|
+
# @example Using result_data alias (batch processing pattern)
|
|
149
|
+
# success(
|
|
150
|
+
# result_data: { batch_processing_outcome: outcome },
|
|
151
|
+
# metadata: { worker_count: 5 }
|
|
152
|
+
# )
|
|
153
|
+
def success(result: nil, result_data: nil, metadata: {})
|
|
154
|
+
# Accept either result: or result_data: for flexibility
|
|
155
|
+
actual_result = result || result_data
|
|
156
|
+
|
|
157
|
+
raise ArgumentError, 'result or result_data required' if actual_result.nil?
|
|
158
|
+
|
|
159
|
+
TaskerCore::Types::StepHandlerCallResult.success(
|
|
160
|
+
result: actual_result,
|
|
161
|
+
metadata: metadata
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Return a standardized error result from a step handler
|
|
166
|
+
#
|
|
167
|
+
# This wraps TaskerCore::Types::StepHandlerCallResult.error() to provide
|
|
168
|
+
# a convenient API for handlers to return error results.
|
|
169
|
+
#
|
|
170
|
+
# Note: In most cases, you should raise TaskerCore exceptions instead
|
|
171
|
+
# of returning error results. This method is provided for cases where
|
|
172
|
+
# you need to return an error without raising an exception.
|
|
173
|
+
#
|
|
174
|
+
# @param message [String] Human-readable error message
|
|
175
|
+
# @param error_type [String] Type of error (PermanentError, RetryableError, etc.)
|
|
176
|
+
# @param error_code [String, nil] Optional error code
|
|
177
|
+
# @param retryable [Boolean] Whether to retry this error
|
|
178
|
+
# @param metadata [Hash] Additional error context
|
|
179
|
+
# @return [TaskerCore::Types::StepHandlerCallResult::Error] Error result
|
|
180
|
+
#
|
|
181
|
+
# @example Permanent error
|
|
182
|
+
# failure(
|
|
183
|
+
# message: "Invalid order total",
|
|
184
|
+
# error_type: "PermanentError",
|
|
185
|
+
# error_code: "INVALID_TOTAL",
|
|
186
|
+
# retryable: false,
|
|
187
|
+
# metadata: { total: -50 }
|
|
188
|
+
# )
|
|
189
|
+
#
|
|
190
|
+
# @example Retryable error
|
|
191
|
+
# failure(
|
|
192
|
+
# message: "API temporarily unavailable",
|
|
193
|
+
# error_type: "RetryableError",
|
|
194
|
+
# retryable: true,
|
|
195
|
+
# metadata: { service: "payment_gateway" }
|
|
196
|
+
# )
|
|
197
|
+
def failure(message:, error_type: 'UnexpectedError', error_code: nil, retryable: false, metadata: {})
|
|
198
|
+
TaskerCore::Types::StepHandlerCallResult.error(
|
|
199
|
+
error_type: error_type,
|
|
200
|
+
message: message,
|
|
201
|
+
error_code: error_code,
|
|
202
|
+
retryable: retryable,
|
|
203
|
+
metadata: metadata
|
|
204
|
+
)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# ========================================================================
|
|
208
|
+
# RAILS ENGINE INTEGRATION NOTES
|
|
209
|
+
# ========================================================================
|
|
210
|
+
|
|
211
|
+
# NOTE: In the Rails engine execution flow, step handlers are called via:
|
|
212
|
+
# 1. TaskHandler.handle(task) → WorkflowCoordinator
|
|
213
|
+
# 2. WorkflowCoordinator → TaskHandler.handle_one_step(task, sequence, step)
|
|
214
|
+
# 3. handle_one_step → get_step_handler(step) → step_handler.call(task, sequence, step)
|
|
215
|
+
#
|
|
216
|
+
# The Rust foundation provides the same flow, so there's no need for a separate
|
|
217
|
+
# execute_step method. The Rails engine signature process(task, sequence, step)
|
|
218
|
+
# is the main entry point that frameworks call.
|
|
219
|
+
#
|
|
220
|
+
# The lifecycle management (events, error handling, retries) is handled by:
|
|
221
|
+
# - Rust orchestration layer with TaskHandler/WorkflowCoordinator foundation
|
|
222
|
+
# - Ruby subclasses provide business logic hooks via call()
|
|
223
|
+
|
|
224
|
+
# ========================================================================
|
|
225
|
+
# INTERNAL PROCESSING METHODS
|
|
226
|
+
# ========================================================================
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
# Validate step configuration before execution
|
|
231
|
+
def validate_step_configuration(config_hash)
|
|
232
|
+
# Basic validation - subclasses can override for specific requirements
|
|
233
|
+
config_hash.is_a?(Hash)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Extract step information for logging
|
|
237
|
+
def extract_step_info(step)
|
|
238
|
+
{
|
|
239
|
+
step_id: extract_attribute(step, :id) || extract_attribute(step, :workflow_step_uuid),
|
|
240
|
+
step_name: extract_attribute(step, :name),
|
|
241
|
+
step_class: step.class.name
|
|
242
|
+
}
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Extract task information for logging
|
|
246
|
+
def extract_task_info(task)
|
|
247
|
+
{
|
|
248
|
+
task_uuid: extract_attribute(task, :task_uuid) || extract_attribute(task, :id),
|
|
249
|
+
task_class: task.class.name
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Safe attribute extraction from Ruby objects
|
|
254
|
+
def extract_attribute(object, attribute)
|
|
255
|
+
object.respond_to?(attribute) ? object.send(attribute) : nil
|
|
256
|
+
rescue StandardError
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Validate process method result
|
|
261
|
+
def validate_process_result(result)
|
|
262
|
+
unless result.is_a?(Hash)
|
|
263
|
+
raise TaskerCore::PermanentError.new(
|
|
264
|
+
"Step process method must return Hash, got #{result.class}",
|
|
265
|
+
error_code: 'INVALID_PROCESS_RESULT',
|
|
266
|
+
error_category: 'validation'
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
return if result.key?(:status) || result.key?('status')
|
|
271
|
+
|
|
272
|
+
logger.warn('Step process result missing status field - assuming success')
|
|
273
|
+
result[:status] = 'completed'
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Classify unexpected errors into permanent vs retryable
|
|
277
|
+
def classify_unexpected_error(error, _context)
|
|
278
|
+
case error
|
|
279
|
+
when ArgumentError, TypeError, NoMethodError
|
|
280
|
+
# Programming errors are permanent
|
|
281
|
+
TaskerCore::PermanentError.new(
|
|
282
|
+
"Programming error in step handler: #{error.message}",
|
|
283
|
+
error_code: 'PROGRAMMING_ERROR',
|
|
284
|
+
error_category: 'validation',
|
|
285
|
+
context: { original_error: error.class.name, backtrace: error.backtrace&.first(5) }
|
|
286
|
+
)
|
|
287
|
+
when Timeout::Error
|
|
288
|
+
# Timeouts are retryable
|
|
289
|
+
TaskerCore::RetryableError.new(
|
|
290
|
+
"Step handler timeout: #{error.message}",
|
|
291
|
+
error_category: 'timeout',
|
|
292
|
+
context: { timeout_class: error.class.name }
|
|
293
|
+
)
|
|
294
|
+
when IOError, SystemCallError
|
|
295
|
+
# IO errors are usually retryable
|
|
296
|
+
TaskerCore::RetryableError.new(
|
|
297
|
+
"IO error in step handler: #{error.message}",
|
|
298
|
+
error_category: 'network',
|
|
299
|
+
context: { io_error_class: error.class.name }
|
|
300
|
+
)
|
|
301
|
+
else
|
|
302
|
+
# Unknown errors - assume retryable for safety
|
|
303
|
+
logger.error("Unexpected error in step handler: #{error.class} - #{error.message}")
|
|
304
|
+
TaskerCore::RetryableError.new(
|
|
305
|
+
"Unexpected error in step handler: #{error.message}",
|
|
306
|
+
error_category: 'unknown',
|
|
307
|
+
context: {
|
|
308
|
+
original_error: error.class.name,
|
|
309
|
+
backtrace: error.backtrace&.first(5)
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Format successful result for Rust layer
|
|
316
|
+
def format_success_result(result, duration)
|
|
317
|
+
{
|
|
318
|
+
status: 'completed',
|
|
319
|
+
success: true,
|
|
320
|
+
data: result,
|
|
321
|
+
duration_seconds: duration,
|
|
322
|
+
handler: handler_name,
|
|
323
|
+
timestamp: Time.now.iso8601
|
|
324
|
+
}
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Format error result for Rust layer
|
|
328
|
+
def format_error_result(error, duration, permanent:)
|
|
329
|
+
{
|
|
330
|
+
status: 'error',
|
|
331
|
+
success: false,
|
|
332
|
+
error: {
|
|
333
|
+
message: error.message,
|
|
334
|
+
type: error.class.name,
|
|
335
|
+
permanent: permanent,
|
|
336
|
+
retry_after: error.respond_to?(:retry_after) ? error.retry_after : nil,
|
|
337
|
+
error_code: error.respond_to?(:error_code) ? error.error_code : nil,
|
|
338
|
+
error_category: error.respond_to?(:error_category) ? error.error_category : 'unknown',
|
|
339
|
+
context: error.respond_to?(:context) ? error.context : {}
|
|
340
|
+
},
|
|
341
|
+
duration_seconds: duration,
|
|
342
|
+
handler: handler_name,
|
|
343
|
+
timestamp: Time.now.iso8601
|
|
344
|
+
}
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Default Rust integration
|
|
348
|
+
def default_rust_integration
|
|
349
|
+
# Return nil - integration is optional
|
|
350
|
+
nil
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'mixins'
|
|
5
|
+
|
|
6
|
+
module TaskerCore
|
|
7
|
+
module StepHandler
|
|
8
|
+
# Batchable step handler for batch processing handlers
|
|
9
|
+
#
|
|
10
|
+
# ## TAS-112: Composition Pattern (DEPRECATED CLASS)
|
|
11
|
+
#
|
|
12
|
+
# This class is provided for backward compatibility. For new code, use the mixin pattern:
|
|
13
|
+
#
|
|
14
|
+
# ```ruby
|
|
15
|
+
# class CsvBatchProcessorHandler < TaskerCore::StepHandler::Base
|
|
16
|
+
# include TaskerCore::StepHandler::Mixins::Batchable
|
|
17
|
+
#
|
|
18
|
+
# def call(context)
|
|
19
|
+
# batch_ctx = get_batch_context(context)
|
|
20
|
+
#
|
|
21
|
+
# # Handle no-op placeholder
|
|
22
|
+
# no_op_result = handle_no_op_worker(batch_ctx)
|
|
23
|
+
# return no_op_result if no_op_result
|
|
24
|
+
#
|
|
25
|
+
# # Handler-specific processing...
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# ```
|
|
29
|
+
#
|
|
30
|
+
# ## TAS-112: 0-Indexed Cursors (BREAKING CHANGE)
|
|
31
|
+
#
|
|
32
|
+
# As of TAS-112, cursor indexing is 0-based to match Python, TypeScript, and Rust.
|
|
33
|
+
# Previously Ruby used 1-based indexing.
|
|
34
|
+
#
|
|
35
|
+
# ## IMPORTANT: Outcome Helper Methods Return Success Objects
|
|
36
|
+
#
|
|
37
|
+
# The outcome helper methods return fully-wrapped Success objects:
|
|
38
|
+
#
|
|
39
|
+
# ```ruby
|
|
40
|
+
# def call(context)
|
|
41
|
+
# if dataset_empty?
|
|
42
|
+
# return no_batches_outcome(reason: 'empty_dataset') # Returns Success
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
# ```
|
|
46
|
+
class Batchable < Base
|
|
47
|
+
include Mixins::Batchable
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'mixins'
|
|
5
|
+
require_relative '../types/decision_point_outcome'
|
|
6
|
+
require_relative '../types/step_handler_call_result'
|
|
7
|
+
|
|
8
|
+
module TaskerCore
|
|
9
|
+
module StepHandler
|
|
10
|
+
# Decision step handler for TAS-53 Dynamic Workflow Decision Points
|
|
11
|
+
#
|
|
12
|
+
# ## TAS-112: Composition Pattern (DEPRECATED CLASS)
|
|
13
|
+
#
|
|
14
|
+
# This class is provided for backward compatibility. For new code, use the mixin pattern:
|
|
15
|
+
#
|
|
16
|
+
# ```ruby
|
|
17
|
+
# class MyDecisionHandler < TaskerCore::StepHandler::Base
|
|
18
|
+
# include TaskerCore::StepHandler::Mixins::Decision
|
|
19
|
+
#
|
|
20
|
+
# def call(context)
|
|
21
|
+
# amount = context.get_task_field('amount')
|
|
22
|
+
#
|
|
23
|
+
# if amount < 1000
|
|
24
|
+
# decision_success(
|
|
25
|
+
# steps: ['auto_approve'],
|
|
26
|
+
# result_data: { route_type: 'auto', amount: amount }
|
|
27
|
+
# )
|
|
28
|
+
# else
|
|
29
|
+
# decision_success(
|
|
30
|
+
# steps: ['manager_approval', 'finance_review'],
|
|
31
|
+
# result_data: { route_type: 'dual', amount: amount }
|
|
32
|
+
# )
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
# ```
|
|
37
|
+
#
|
|
38
|
+
# ## No-Branch Pattern
|
|
39
|
+
#
|
|
40
|
+
# ```ruby
|
|
41
|
+
# def call(context)
|
|
42
|
+
# if context.get_task_field('skip_approval')
|
|
43
|
+
# decision_no_branches(result_data: { reason: 'skipped' })
|
|
44
|
+
# else
|
|
45
|
+
# decision_success(steps: ['standard_approval'])
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
# ```
|
|
49
|
+
class Decision < Base
|
|
50
|
+
include Mixins::Decision
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|