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,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-struct'
|
|
4
|
+
require 'dry-types'
|
|
5
|
+
|
|
6
|
+
module TaskerCore
|
|
7
|
+
module Types
|
|
8
|
+
# Include Dry.Types for access to Types::String, etc.
|
|
9
|
+
include Dry.Types()
|
|
10
|
+
|
|
11
|
+
# UUID validation regex pattern (defined at module level for reuse)
|
|
12
|
+
# Updated to accept UUID v7 (version nibble can be 7)
|
|
13
|
+
UUID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
|
14
|
+
|
|
15
|
+
# Simple message structure for UUID-based step processing
|
|
16
|
+
#
|
|
17
|
+
# This replaces the complex nested StepMessage with a minimal 3-field structure
|
|
18
|
+
# that leverages the shared database as the API layer. Ruby workers receive this
|
|
19
|
+
# simple message and use the UUIDs to fetch ActiveRecord models directly.
|
|
20
|
+
#
|
|
21
|
+
# Benefits:
|
|
22
|
+
# - 80%+ message size reduction (3 UUIDs vs complex nested JSON)
|
|
23
|
+
# - Eliminates type conversion issues (no hash-to-object conversion)
|
|
24
|
+
# - Prevents stale queue messages (UUIDs are globally unique)
|
|
25
|
+
# - Real ActiveRecord models for handlers (full ORM functionality)
|
|
26
|
+
# - Database as single source of truth
|
|
27
|
+
#
|
|
28
|
+
# @example Message structure
|
|
29
|
+
# {
|
|
30
|
+
# "task_uuid": "550e8400-e29b-41d4-a716-446655440001",
|
|
31
|
+
# "step_uuid": "550e8400-e29b-41d4-a716-446655440002",
|
|
32
|
+
# "ready_dependency_step_uuids": [
|
|
33
|
+
# "550e8400-e29b-41d4-a716-446655440003",
|
|
34
|
+
# "550e8400-e29b-41d4-a716-446655440004"
|
|
35
|
+
# ]
|
|
36
|
+
# }
|
|
37
|
+
#
|
|
38
|
+
# @example Ruby processing
|
|
39
|
+
# # 1. Receive simple message
|
|
40
|
+
# task = TaskerCore::Database::Models::Task.find_by!(task_uuid: message.task_uuid)
|
|
41
|
+
# step = TaskerCore::Database::Models::WorkflowStep.find_by!(step_uuid: message.step_uuid)
|
|
42
|
+
# dependencies = TaskerCore::Database::Models::WorkflowStep.where(
|
|
43
|
+
# step_uuid: message.ready_dependency_step_uuids
|
|
44
|
+
# ).includes(:results)
|
|
45
|
+
#
|
|
46
|
+
# # 2. Create context and call handler
|
|
47
|
+
# context = TaskerCore::Types::StepContext.new(step_data)
|
|
48
|
+
# handler.call(context)
|
|
49
|
+
class SimpleStepMessage < Dry::Struct
|
|
50
|
+
# Make the struct flexible for additional attributes if needed
|
|
51
|
+
transform_keys(&:to_sym)
|
|
52
|
+
|
|
53
|
+
# Task UUID from tasker_tasks.task_uuid
|
|
54
|
+
attribute :task_uuid, Types::String.constrained(format: UUID_REGEX)
|
|
55
|
+
|
|
56
|
+
# Step UUID from tasker_workflow_steps.step_uuid
|
|
57
|
+
attribute :step_uuid, Types::String.constrained(format: UUID_REGEX)
|
|
58
|
+
|
|
59
|
+
# Array of dependency step UUIDs that are ready/completed
|
|
60
|
+
# Empty array means no dependencies or root step
|
|
61
|
+
attribute :ready_dependency_step_uuids, Types::Array.of(
|
|
62
|
+
Types::String.constrained(format: UUID_REGEX)
|
|
63
|
+
).default([].freeze)
|
|
64
|
+
|
|
65
|
+
# Convert to hash for JSON serialization
|
|
66
|
+
# @return [Hash] hash representation suitable for JSON
|
|
67
|
+
def to_h
|
|
68
|
+
{
|
|
69
|
+
task_uuid: task_uuid,
|
|
70
|
+
step_uuid: step_uuid,
|
|
71
|
+
ready_dependency_step_uuids: ready_dependency_step_uuids
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Create from hash (for deserialization from JSON)
|
|
76
|
+
# @param hash [Hash] hash representation
|
|
77
|
+
# @return [SimpleStepMessage] new simple message instance
|
|
78
|
+
def self.from_hash(hash)
|
|
79
|
+
symbolized = hash.transform_keys(&:to_sym)
|
|
80
|
+
new(symbolized)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Validate that all UUIDs exist in the database
|
|
84
|
+
# @return [Boolean] true if all referenced records exist
|
|
85
|
+
def valid_references?
|
|
86
|
+
task_exists? && step_exists? && all_dependencies_exist?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Check if the task UUID exists in the database
|
|
90
|
+
# @return [Boolean] true if task exists
|
|
91
|
+
def task_exists?
|
|
92
|
+
TaskerCore::Database::Models::Task.exists?(task_uuid: task_uuid)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check if the step UUID exists in the database
|
|
96
|
+
# @return [Boolean] true if step exists
|
|
97
|
+
def step_exists?
|
|
98
|
+
TaskerCore::Database::Models::WorkflowStep.exists?(workflow_step_uuid: step_uuid)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Check if all dependency UUIDs exist in the database
|
|
102
|
+
# @return [Boolean] true if all dependencies exist
|
|
103
|
+
def all_dependencies_exist?
|
|
104
|
+
return true if ready_dependency_step_uuids.empty?
|
|
105
|
+
|
|
106
|
+
existing_count = TaskerCore::Database::Models::WorkflowStep
|
|
107
|
+
.where(workflow_step_uuid: ready_dependency_step_uuids)
|
|
108
|
+
.count
|
|
109
|
+
|
|
110
|
+
existing_count == ready_dependency_step_uuids.length
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Fetch the actual task record from the database
|
|
114
|
+
# @return [TaskerCore::Database::Models::Task] the task record
|
|
115
|
+
# @raise [ActiveRecord::RecordNotFound] if task doesn't exist
|
|
116
|
+
def fetch_task
|
|
117
|
+
TaskerCore::Database::Models::Task.find_by!(task_uuid: task_uuid)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Fetch the actual step record from the database
|
|
121
|
+
# @return [TaskerCore::Database::Models::WorkflowStep] the step record
|
|
122
|
+
# @raise [ActiveRecord::RecordNotFound] if step doesn't exist
|
|
123
|
+
def fetch_step
|
|
124
|
+
TaskerCore::Database::Models::WorkflowStep.find_by!(workflow_step_uuid: step_uuid)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Fetch the dependency step records from the database
|
|
128
|
+
# @return [ActiveRecord::Relation<TaskerCore::Database::Models::WorkflowStep>] dependency steps
|
|
129
|
+
def fetch_dependencies
|
|
130
|
+
return TaskerCore::Database::Models::WorkflowStep.none if ready_dependency_step_uuids.empty?
|
|
131
|
+
|
|
132
|
+
TaskerCore::Database::Models::WorkflowStep
|
|
133
|
+
.where(workflow_step_uuid: ready_dependency_step_uuids)
|
|
134
|
+
.includes(:results, :named_step) # Preload commonly needed associations
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Create a step message for testing with generated UUIDs
|
|
138
|
+
# @param task_uuid [String] task UUID
|
|
139
|
+
# @param step_uuid [String] step UUID
|
|
140
|
+
# @param ready_dependency_step_uuids [Array<String>] dependency step UUIDs
|
|
141
|
+
# @return [SimpleStepMessage] new simple message
|
|
142
|
+
def self.build_test(task_uuid:, step_uuid:, ready_dependency_step_uuids: [])
|
|
143
|
+
new(
|
|
144
|
+
task_uuid: task_uuid,
|
|
145
|
+
step_uuid: step_uuid,
|
|
146
|
+
ready_dependency_step_uuids: ready_dependency_step_uuids
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TaskerCore
|
|
4
|
+
module Types
|
|
5
|
+
# StepContext provides a unified context for step handler execution.
|
|
6
|
+
#
|
|
7
|
+
# This is the cross-language standard context object passed to handler.call(context).
|
|
8
|
+
# It wraps the FFI-provided TaskSequenceStepWrapper and adds convenience accessors
|
|
9
|
+
# that match Python and Rust naming conventions.
|
|
10
|
+
#
|
|
11
|
+
# Cross-language standard fields:
|
|
12
|
+
# - task_uuid: UUID of the task
|
|
13
|
+
# - step_uuid: UUID of the workflow step
|
|
14
|
+
# - input_data: Step input data from workflow_step.inputs
|
|
15
|
+
# - step_inputs: Alias for input_data
|
|
16
|
+
# - step_config: Handler configuration from step_definition.handler.initialization
|
|
17
|
+
# - retry_count: Current retry attempt count
|
|
18
|
+
# - max_retries: Maximum retry attempts allowed
|
|
19
|
+
# - dependency_results: Results from parent steps
|
|
20
|
+
#
|
|
21
|
+
# Ruby-specific accessors (for backward compatibility):
|
|
22
|
+
# - task: TaskWrapper instance
|
|
23
|
+
# - workflow_step: WorkflowStepWrapper instance
|
|
24
|
+
# - step_definition: StepDefinitionWrapper instance
|
|
25
|
+
#
|
|
26
|
+
# @example Accessing context in a handler
|
|
27
|
+
# def call(context)
|
|
28
|
+
# # Cross-language standard fields
|
|
29
|
+
# task_uuid = context.task_uuid
|
|
30
|
+
# step_uuid = context.step_uuid
|
|
31
|
+
# input_data = context.input_data
|
|
32
|
+
# deps = context.dependency_results
|
|
33
|
+
#
|
|
34
|
+
# # Convenience methods
|
|
35
|
+
# even_number = context.get_task_field('even_number')
|
|
36
|
+
# prev_result = context.get_dependency_result('step_1')
|
|
37
|
+
#
|
|
38
|
+
# # Ruby-specific accessors (backward compat)
|
|
39
|
+
# task = context.task
|
|
40
|
+
# step = context.workflow_step
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# @see TaskerCore::Models::TaskSequenceStepWrapper The underlying wrapper
|
|
44
|
+
class StepContext
|
|
45
|
+
# @return [TaskerCore::Models::TaskWrapper] Task metadata and context
|
|
46
|
+
attr_reader :task
|
|
47
|
+
|
|
48
|
+
# @return [TaskerCore::Models::WorkflowStepWrapper] Step execution state
|
|
49
|
+
attr_reader :workflow_step
|
|
50
|
+
|
|
51
|
+
# @return [TaskerCore::Models::DependencyResultsWrapper] Results from parent steps
|
|
52
|
+
attr_reader :dependency_results
|
|
53
|
+
|
|
54
|
+
# @return [TaskerCore::Models::StepDefinitionWrapper] Step definition from template
|
|
55
|
+
attr_reader :step_definition
|
|
56
|
+
|
|
57
|
+
# @return [String] The handler name for this step
|
|
58
|
+
attr_reader :handler_name
|
|
59
|
+
|
|
60
|
+
# Creates a StepContext from FFI step data
|
|
61
|
+
#
|
|
62
|
+
# @param step_data [Hash, TaskerCore::Models::TaskSequenceStepWrapper] The step data from Rust FFI
|
|
63
|
+
# @param handler_name [String, nil] Optional handler name override
|
|
64
|
+
def initialize(step_data, handler_name: nil)
|
|
65
|
+
if step_data.is_a?(TaskerCore::Models::TaskSequenceStepWrapper)
|
|
66
|
+
@task = step_data.task
|
|
67
|
+
@workflow_step = step_data.workflow_step
|
|
68
|
+
@dependency_results = step_data.dependency_results
|
|
69
|
+
@step_definition = step_data.step_definition
|
|
70
|
+
else
|
|
71
|
+
wrapper = TaskerCore::Models::TaskSequenceStepWrapper.new(step_data)
|
|
72
|
+
@task = wrapper.task
|
|
73
|
+
@workflow_step = wrapper.workflow_step
|
|
74
|
+
@dependency_results = wrapper.dependency_results
|
|
75
|
+
@step_definition = wrapper.step_definition
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@handler_name = handler_name || @step_definition.handler&.callable
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# ========================================================================
|
|
82
|
+
# CROSS-LANGUAGE STANDARD FIELDS
|
|
83
|
+
# ========================================================================
|
|
84
|
+
|
|
85
|
+
# Cross-language standard: task_uuid
|
|
86
|
+
# @return [String] UUID of the task
|
|
87
|
+
def task_uuid
|
|
88
|
+
@task.task_uuid
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Cross-language standard: step_uuid
|
|
92
|
+
# @return [String] UUID of the workflow step
|
|
93
|
+
def step_uuid
|
|
94
|
+
@workflow_step.workflow_step_uuid
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Cross-language standard: input_data
|
|
98
|
+
# Returns the step inputs from the workflow step.
|
|
99
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Step input data
|
|
100
|
+
def input_data
|
|
101
|
+
@workflow_step.inputs
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Cross-language standard: step_inputs (alias for input_data)
|
|
105
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Step input data
|
|
106
|
+
alias step_inputs input_data
|
|
107
|
+
|
|
108
|
+
# Cross-language standard: step_config
|
|
109
|
+
# Returns the handler configuration from the step definition.
|
|
110
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Handler configuration from template
|
|
111
|
+
def step_config
|
|
112
|
+
@step_definition.handler&.initialization || {}.with_indifferent_access
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Cross-language standard: retry_count
|
|
116
|
+
# @return [Integer] Current retry attempt count
|
|
117
|
+
def retry_count
|
|
118
|
+
@workflow_step.attempts || 0
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Cross-language standard: max_retries
|
|
122
|
+
# @return [Integer] Maximum retry attempts allowed
|
|
123
|
+
def max_retries
|
|
124
|
+
@workflow_step.max_attempts || 3
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# ========================================================================
|
|
128
|
+
# CONVENIENCE METHODS
|
|
129
|
+
# ========================================================================
|
|
130
|
+
|
|
131
|
+
# Get a field from the task context.
|
|
132
|
+
#
|
|
133
|
+
# @param field_name [String, Symbol] Field name in task context
|
|
134
|
+
# @return [Object, nil] The field value or nil if not found
|
|
135
|
+
#
|
|
136
|
+
# @example
|
|
137
|
+
# even_number = context.get_task_field('even_number')
|
|
138
|
+
def get_task_field(field_name)
|
|
139
|
+
@task.context[field_name.to_s]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Cross-language standard alias for get_task_field
|
|
143
|
+
# @see #get_task_field
|
|
144
|
+
alias get_input get_task_field
|
|
145
|
+
|
|
146
|
+
# Get a field from the task context with a default value.
|
|
147
|
+
#
|
|
148
|
+
# @param field_name [String, Symbol] Field name in task context
|
|
149
|
+
# @param default [Object] Default value if field is nil
|
|
150
|
+
# @return [Object] The field value or default if nil
|
|
151
|
+
#
|
|
152
|
+
# @example
|
|
153
|
+
# batch_size = context.get_input_or('batch_size', 100)
|
|
154
|
+
def get_input_or(field_name, default = nil)
|
|
155
|
+
value = get_task_field(field_name)
|
|
156
|
+
value.nil? ? default : value
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get a configuration value from the handler initialization.
|
|
160
|
+
#
|
|
161
|
+
# @param key [String, Symbol] Configuration key
|
|
162
|
+
# @return [Object, nil] The configuration value or nil if not found
|
|
163
|
+
#
|
|
164
|
+
# @example
|
|
165
|
+
# api_url = context.get_config('api_url')
|
|
166
|
+
def get_config(key)
|
|
167
|
+
step_config[key.to_s]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Get a dependency result from a parent step.
|
|
171
|
+
#
|
|
172
|
+
# This returns the actual result value, not the full metadata hash.
|
|
173
|
+
# Use dependency_results.get_result(step_name) for full metadata.
|
|
174
|
+
#
|
|
175
|
+
# @param step_name [String, Symbol] Name of the parent step
|
|
176
|
+
# @return [Object, nil] The result value or nil if not found
|
|
177
|
+
#
|
|
178
|
+
# @example
|
|
179
|
+
# prev_result = context.get_dependency_result('step_1')
|
|
180
|
+
def get_dependency_result(step_name)
|
|
181
|
+
@dependency_results.get_results(step_name)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Extract a nested field from a dependency result.
|
|
185
|
+
#
|
|
186
|
+
# Useful when dependency results are complex objects and you need
|
|
187
|
+
# to extract a specific nested value.
|
|
188
|
+
#
|
|
189
|
+
# @param step_name [String, Symbol] Name of the parent step
|
|
190
|
+
# @param path [Array<String, Symbol>] Path to the nested field
|
|
191
|
+
# @return [Object, nil] The nested value or nil if not found
|
|
192
|
+
#
|
|
193
|
+
# @example
|
|
194
|
+
# csv_path = context.get_dependency_field('analyze_csv', 'csv_file_path')
|
|
195
|
+
# nested_value = context.get_dependency_field('step_1', 'data', 'items', 0)
|
|
196
|
+
def get_dependency_field(step_name, *path)
|
|
197
|
+
result = get_dependency_result(step_name)
|
|
198
|
+
return nil if result.nil?
|
|
199
|
+
|
|
200
|
+
result.dig(*path.map(&:to_s))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# ========================================================================
|
|
204
|
+
# RETRY HELPERS
|
|
205
|
+
# ========================================================================
|
|
206
|
+
|
|
207
|
+
# Check if this execution is a retry attempt.
|
|
208
|
+
#
|
|
209
|
+
# @return [Boolean] true if retry_count > 0
|
|
210
|
+
#
|
|
211
|
+
# @example
|
|
212
|
+
# if context.is_retry?
|
|
213
|
+
# logger.info("Retrying step, attempt #{context.retry_count}")
|
|
214
|
+
# end
|
|
215
|
+
def is_retry?
|
|
216
|
+
retry_count.positive?
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Check if this is the last retry attempt.
|
|
220
|
+
#
|
|
221
|
+
# @return [Boolean] true if this is the final retry attempt
|
|
222
|
+
#
|
|
223
|
+
# @example
|
|
224
|
+
# if context.is_last_retry?
|
|
225
|
+
# # Send alert or take special action on final attempt
|
|
226
|
+
# end
|
|
227
|
+
def is_last_retry?
|
|
228
|
+
retry_count >= max_retries - 1
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# ========================================================================
|
|
232
|
+
# CHECKPOINT ACCESSORS (TAS-125 Batch Processing Support)
|
|
233
|
+
# ========================================================================
|
|
234
|
+
|
|
235
|
+
# Get the raw checkpoint data from the workflow step.
|
|
236
|
+
#
|
|
237
|
+
# @return [Hash, nil] The checkpoint data or nil if not set
|
|
238
|
+
def checkpoint
|
|
239
|
+
@workflow_step.checkpoint
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Get the checkpoint cursor position.
|
|
243
|
+
#
|
|
244
|
+
# The cursor represents the current position in batch processing,
|
|
245
|
+
# allowing handlers to resume from where they left off.
|
|
246
|
+
#
|
|
247
|
+
# @return [Object, nil] The cursor value (int, string, or object)
|
|
248
|
+
#
|
|
249
|
+
# @example
|
|
250
|
+
# cursor = context.checkpoint_cursor
|
|
251
|
+
# start_from = cursor || 0
|
|
252
|
+
def checkpoint_cursor
|
|
253
|
+
checkpoint&.dig('cursor')
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Get the number of items processed in the current batch run.
|
|
257
|
+
#
|
|
258
|
+
# @return [Integer] Number of items processed (0 if no checkpoint)
|
|
259
|
+
def checkpoint_items_processed
|
|
260
|
+
checkpoint&.dig('items_processed') || 0
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Get the accumulated results from batch processing.
|
|
264
|
+
#
|
|
265
|
+
# Accumulated results allow handlers to maintain running totals
|
|
266
|
+
# or aggregated state across checkpoint boundaries.
|
|
267
|
+
#
|
|
268
|
+
# @return [Hash, nil] The accumulated results or nil if not set
|
|
269
|
+
#
|
|
270
|
+
# @example
|
|
271
|
+
# totals = context.accumulated_results || {}
|
|
272
|
+
# current_sum = totals['sum'] || 0
|
|
273
|
+
def accumulated_results
|
|
274
|
+
checkpoint&.dig('accumulated_results')
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Check if a checkpoint exists for this step.
|
|
278
|
+
#
|
|
279
|
+
# @return [Boolean] true if a checkpoint cursor exists
|
|
280
|
+
#
|
|
281
|
+
# @example
|
|
282
|
+
# if context.has_checkpoint?
|
|
283
|
+
# logger.info("Resuming from checkpoint at cursor: #{context.checkpoint_cursor}")
|
|
284
|
+
# end
|
|
285
|
+
def has_checkpoint?
|
|
286
|
+
!checkpoint_cursor.nil?
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# ========================================================================
|
|
290
|
+
# ADDITIONAL ACCESSORS
|
|
291
|
+
# ========================================================================
|
|
292
|
+
|
|
293
|
+
# @return [String, nil] Namespace name from task template
|
|
294
|
+
def namespace_name
|
|
295
|
+
@task.respond_to?(:namespace_name) ? @task.namespace_name : nil
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# @return [String] Step name from workflow step
|
|
299
|
+
def step_name
|
|
300
|
+
@workflow_step.name
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Full task context
|
|
304
|
+
def context
|
|
305
|
+
@task.context
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# @return [Boolean] Whether the step can be retried
|
|
309
|
+
def retryable?
|
|
310
|
+
@workflow_step.retryable
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# String representation for debugging
|
|
314
|
+
def to_s
|
|
315
|
+
"#<StepContext task_uuid=#{task_uuid} step_uuid=#{step_uuid} step_name=#{step_name}>"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Detailed inspection for debugging
|
|
319
|
+
def inspect
|
|
320
|
+
"#<StepContext:#{object_id} " \
|
|
321
|
+
"task_uuid=#{task_uuid.inspect} " \
|
|
322
|
+
"step_uuid=#{step_uuid.inspect} " \
|
|
323
|
+
"step_name=#{step_name.inspect} " \
|
|
324
|
+
"handler_name=#{handler_name.inspect}>"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|