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,240 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-types'
|
|
4
|
+
require 'dry-struct'
|
|
5
|
+
require 'time'
|
|
6
|
+
|
|
7
|
+
module TaskerCore
|
|
8
|
+
module Types
|
|
9
|
+
# TaskTemplate Types - Self-Describing Workflow Configuration
|
|
10
|
+
#
|
|
11
|
+
# This module implements the self-describing TaskTemplate structure, featuring:
|
|
12
|
+
# - Callable-based handlers for maximum flexibility
|
|
13
|
+
# - Structured handler initialization configuration
|
|
14
|
+
# - Clear system dependency declarations
|
|
15
|
+
# - First-class domain event support
|
|
16
|
+
# - Enhanced environment-specific overrides
|
|
17
|
+
# - JSON Schema-based input validation
|
|
18
|
+
|
|
19
|
+
module Types
|
|
20
|
+
include Dry.Types()
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Template metadata for documentation and discovery
|
|
24
|
+
class TemplateMetadata < Dry::Struct
|
|
25
|
+
attribute :author, Types::String.optional.default(nil)
|
|
26
|
+
attribute :tags, Types::Array.of(Types::String).default([].freeze)
|
|
27
|
+
attribute :documentation_url, Types::String.optional.default(nil)
|
|
28
|
+
attribute :created_at, Types::String.optional.default(nil) # ISO8601 format
|
|
29
|
+
attribute :updated_at, Types::String.optional.default(nil) # ISO8601 format
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Handler definition with callable and initialization
|
|
33
|
+
#
|
|
34
|
+
# TAS-93: Extended with handler_method and resolver fields for the resolver chain pattern.
|
|
35
|
+
# - `handler_method`: Optional method name override (defaults to 'call')
|
|
36
|
+
# - `resolver`: Optional resolver hint to bypass inferential resolution
|
|
37
|
+
#
|
|
38
|
+
# NOTE: We use `handler_method` instead of `method` to avoid collision with
|
|
39
|
+
# Ruby's built-in `Object#method`.
|
|
40
|
+
class HandlerDefinition < Dry::Struct
|
|
41
|
+
attribute :callable, Types::Strict::String
|
|
42
|
+
attribute :initialization, Types::Hash.default({}.freeze)
|
|
43
|
+
# TAS-93: Method name to invoke on the handler (defaults to 'call')
|
|
44
|
+
# Named handler_method to avoid collision with Ruby's Object#method
|
|
45
|
+
attribute :handler_method, Types::String.optional.default(nil)
|
|
46
|
+
# TAS-93: Resolver hint to bypass the resolver chain (e.g., 'payment_resolver')
|
|
47
|
+
attribute :resolver, Types::String.optional.default(nil)
|
|
48
|
+
|
|
49
|
+
# TAS-93: Get the effective method name to invoke
|
|
50
|
+
# Returns the configured method name or defaults to 'call'
|
|
51
|
+
#
|
|
52
|
+
# @return [String] The method name to invoke on the handler
|
|
53
|
+
def effective_method
|
|
54
|
+
handler_method.presence || 'call'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# TAS-93: Check if this handler uses custom method dispatch
|
|
58
|
+
# Returns true if a non-default method is configured
|
|
59
|
+
#
|
|
60
|
+
# @return [Boolean] true if handler_method is something other than 'call'
|
|
61
|
+
def uses_method_dispatch?
|
|
62
|
+
handler_method.present? && handler_method != 'call'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# TAS-93: Check if a resolver hint is specified
|
|
66
|
+
# When true, the resolver chain should skip inferential resolution
|
|
67
|
+
# and directly use the named resolver
|
|
68
|
+
#
|
|
69
|
+
# @return [Boolean] true if resolver hint is present
|
|
70
|
+
def has_resolver_hint?
|
|
71
|
+
resolver.present?
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# External system dependencies
|
|
76
|
+
class SystemDependencies < Dry::Struct
|
|
77
|
+
attribute :primary, Types::String.default('default')
|
|
78
|
+
attribute :secondary, Types::Array.of(Types::String).default([].freeze)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Domain event definition with schema
|
|
82
|
+
class DomainEventDefinition < Dry::Struct
|
|
83
|
+
attribute :name, Types::Strict::String
|
|
84
|
+
attribute :description, Types::String.optional.default(nil)
|
|
85
|
+
attribute :schema, Types::Hash.optional.default(nil) # JSON Schema
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Retry configuration with backoff strategies
|
|
89
|
+
class RetryConfiguration < Dry::Struct
|
|
90
|
+
attribute :retryable, Types::Bool.default(true)
|
|
91
|
+
attribute :limit, Types::Integer.default(3)
|
|
92
|
+
attribute :backoff, Types::String.default('exponential').enum('none', 'linear', 'exponential', 'fibonacci')
|
|
93
|
+
attribute :backoff_base_ms, Types::Integer.optional.default(1000)
|
|
94
|
+
attribute :max_backoff_ms, Types::Integer.optional.default(30_000)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Individual workflow step definition
|
|
98
|
+
class StepDefinition < Dry::Struct
|
|
99
|
+
attribute :name, Types::Strict::String
|
|
100
|
+
attribute :description, Types::String.optional.default(nil)
|
|
101
|
+
attribute :handler, HandlerDefinition
|
|
102
|
+
attribute :system_dependency, Types::String.optional.default(nil)
|
|
103
|
+
attribute :dependencies, Types::Array.of(Types::String).default([].freeze)
|
|
104
|
+
attribute(:retry, RetryConfiguration.default { RetryConfiguration.new })
|
|
105
|
+
attribute :timeout_seconds, Types::Integer.optional.default(nil)
|
|
106
|
+
attribute :publishes_events, Types::Array.of(Types::String).default([].freeze)
|
|
107
|
+
# TAS-53: Decision point step type classification
|
|
108
|
+
# Matches Rust field serialization name (Rust field: step_type, YAML: type)
|
|
109
|
+
attribute :type, Types::String.optional.default(nil).enum('standard', 'decision')
|
|
110
|
+
|
|
111
|
+
# Check if this step depends on another step
|
|
112
|
+
def depends_on?(other_step_name)
|
|
113
|
+
dependencies.include?(other_step_name.to_s)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# TAS-53: Check if this is a decision point step
|
|
117
|
+
def decision?
|
|
118
|
+
type == 'decision'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# TAS-53: Check if this is a standard step
|
|
122
|
+
def standard?
|
|
123
|
+
type.nil? || type == 'standard'
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Handler override for environments
|
|
128
|
+
class HandlerOverride < Dry::Struct
|
|
129
|
+
attribute :initialization, Types::Hash.optional.default(nil)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Step override for environments
|
|
133
|
+
class StepOverride < Dry::Struct
|
|
134
|
+
attribute :name, Types::Strict::String # Step name or "ALL" for all steps
|
|
135
|
+
attribute :handler, HandlerOverride.optional.default(nil)
|
|
136
|
+
attribute :timeout_seconds, Types::Integer.optional.default(nil)
|
|
137
|
+
attribute :retry, RetryConfiguration.optional.default(nil)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Environment-specific overrides
|
|
141
|
+
class EnvironmentOverride < Dry::Struct
|
|
142
|
+
attribute :task_handler, HandlerOverride.optional.default(nil)
|
|
143
|
+
attribute :steps, Types::Array.of(StepOverride).default([].freeze)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Main TaskTemplate structure with self-describing configuration
|
|
147
|
+
class TaskTemplate < Dry::Struct
|
|
148
|
+
# Semantic version pattern validation
|
|
149
|
+
VERSION_PATTERN = /\A\d+\.\d+\.\d+\z/
|
|
150
|
+
|
|
151
|
+
# Core required attributes
|
|
152
|
+
attribute :name, Types::Strict::String
|
|
153
|
+
attribute :namespace_name, Types::Strict::String
|
|
154
|
+
attribute :version, Types::String.constrained(format: VERSION_PATTERN).default('1.0.0')
|
|
155
|
+
|
|
156
|
+
# Self-describing structure
|
|
157
|
+
attribute :description, Types::String.optional.default(nil)
|
|
158
|
+
attribute :metadata, TemplateMetadata.optional.default(nil)
|
|
159
|
+
attribute :task_handler, HandlerDefinition.optional.default(nil)
|
|
160
|
+
attribute(:system_dependencies, SystemDependencies.default { SystemDependencies.new })
|
|
161
|
+
attribute :domain_events, Types::Array.of(DomainEventDefinition).default([].freeze)
|
|
162
|
+
attribute :input_schema, Types::Hash.optional.default(nil) # JSON Schema
|
|
163
|
+
attribute :steps, Types::Array.of(StepDefinition).default([].freeze)
|
|
164
|
+
attribute :environments, Types::Hash.map(Types::String, EnvironmentOverride).default({}.freeze)
|
|
165
|
+
|
|
166
|
+
# Metadata (not persisted to database)
|
|
167
|
+
attribute :loaded_from, Types::String.optional.default(nil)
|
|
168
|
+
|
|
169
|
+
# Generate a unique key for this template
|
|
170
|
+
def template_key
|
|
171
|
+
"#{namespace_name}/#{name}:#{version}"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Extract all callable references
|
|
175
|
+
def all_callables
|
|
176
|
+
callables = []
|
|
177
|
+
callables << task_handler.callable if task_handler
|
|
178
|
+
steps.each { |step| callables << step.handler.callable }
|
|
179
|
+
callables
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Check if template is valid for registration
|
|
183
|
+
def valid_for_registration?
|
|
184
|
+
return false if name.empty? || namespace_name.empty?
|
|
185
|
+
return false unless version.match?(VERSION_PATTERN)
|
|
186
|
+
|
|
187
|
+
# Ensure all steps have callables
|
|
188
|
+
return false if steps.any? { |step| step.handler.callable.empty? }
|
|
189
|
+
|
|
190
|
+
true
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Resolve template for specific environment
|
|
194
|
+
def resolve_for_environment(environment_name)
|
|
195
|
+
resolved_template = deep_dup
|
|
196
|
+
|
|
197
|
+
if environments[environment_name]
|
|
198
|
+
env_override = environments[environment_name]
|
|
199
|
+
|
|
200
|
+
# Apply task handler overrides
|
|
201
|
+
if env_override.task_handler && resolved_template.task_handler && env_override.task_handler.initialization
|
|
202
|
+
resolved_template.task_handler.initialization.merge!(env_override.task_handler.initialization)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Apply step overrides
|
|
206
|
+
env_override.steps.each do |step_override|
|
|
207
|
+
if step_override.name == 'ALL'
|
|
208
|
+
# Apply to all steps
|
|
209
|
+
resolved_template.steps.each { |step| apply_step_override(step, step_override) }
|
|
210
|
+
else
|
|
211
|
+
# Apply to specific step
|
|
212
|
+
step = resolved_template.steps.find { |s| s.name == step_override.name }
|
|
213
|
+
apply_step_override(step, step_override) if step
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
resolved_template
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
# Deep duplicate for environment resolution
|
|
224
|
+
def deep_dup
|
|
225
|
+
# Simple deep dup implementation for dry-struct
|
|
226
|
+
TaskTemplate.new(to_h)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Apply step override to a step
|
|
230
|
+
def apply_step_override(step, step_override)
|
|
231
|
+
if step_override.handler&.initialization
|
|
232
|
+
step.handler.initialization.merge!(step_override.handler.initialization)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
step.timeout_seconds = step_override.timeout_seconds if step_override.timeout_seconds
|
|
236
|
+
step.retry = step_override.retry if step_override.retry
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-struct'
|
|
4
|
+
require 'dry-types'
|
|
5
|
+
|
|
6
|
+
module TaskerCore
|
|
7
|
+
module Types
|
|
8
|
+
# Task-related type definitions for TaskerCore domain objects
|
|
9
|
+
module TaskTypes
|
|
10
|
+
module Types
|
|
11
|
+
include Dry.Types()
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Task request struct with full validation
|
|
15
|
+
class TaskRequest < Dry::Struct
|
|
16
|
+
# Core identification
|
|
17
|
+
attribute :namespace, Types::Coercible::String
|
|
18
|
+
attribute :name, Types::Coercible::String
|
|
19
|
+
attribute :version, Types::Coercible::String.default('1.0.0')
|
|
20
|
+
|
|
21
|
+
# Task state and metadata
|
|
22
|
+
attribute :status, Types::Coercible::String.default('pending').enum(
|
|
23
|
+
'pending',
|
|
24
|
+
'in_progress',
|
|
25
|
+
'completed',
|
|
26
|
+
'failed',
|
|
27
|
+
'cancelled',
|
|
28
|
+
'paused'
|
|
29
|
+
)
|
|
30
|
+
attribute :initiator, Types::Coercible::String
|
|
31
|
+
attribute :source_system, Types::Coercible::String
|
|
32
|
+
attribute :reason, Types::Coercible::String
|
|
33
|
+
attribute :complete, Types::Strict::Bool.default(false)
|
|
34
|
+
attribute :tags, Types::Array.of(Types::Coercible::String).default([].freeze)
|
|
35
|
+
|
|
36
|
+
# Task context (business data)
|
|
37
|
+
attribute :context, Types::Coercible::Hash
|
|
38
|
+
|
|
39
|
+
# Optional fields for advanced use cases
|
|
40
|
+
attribute? :priority, Types::Integer.default(0) # Higher values = higher priority, default 0
|
|
41
|
+
attribute? :claim_timeout_seconds, Types::Integer.default(60) # Timeout for distributed orchestration claims
|
|
42
|
+
attribute? :max_retries, Types::Integer.default(3)
|
|
43
|
+
attribute? :timeout_seconds, Types::Integer.default(300)
|
|
44
|
+
attribute? :parent_task_uuid, Types::String
|
|
45
|
+
attribute? :correlation_id, Types::Coercible::String
|
|
46
|
+
|
|
47
|
+
attribute?(:requested_at, Types::Constructor(Time) do |value|
|
|
48
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
49
|
+
end.default { Time.now })
|
|
50
|
+
|
|
51
|
+
# Validation methods
|
|
52
|
+
def valid_for_creation?
|
|
53
|
+
!namespace.nil? && !namespace.empty? &&
|
|
54
|
+
!name.nil? && !name.empty? &&
|
|
55
|
+
!initiator.nil? && !initiator.empty? &&
|
|
56
|
+
!source_system.nil? && !source_system.empty? &&
|
|
57
|
+
!reason.nil? && !reason.empty? &&
|
|
58
|
+
context.is_a?(Hash)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Convert to hash suitable for FFI serialization
|
|
62
|
+
def to_ffi_hash
|
|
63
|
+
# Build options hash for fields that go in the options field (legacy fields)
|
|
64
|
+
options_hash = {}
|
|
65
|
+
options_hash['max_retries'] = max_retries if max_retries
|
|
66
|
+
options_hash['timeout_seconds'] = timeout_seconds if timeout_seconds
|
|
67
|
+
options_hash['parent_task_uuid'] = parent_task_uuid if parent_task_uuid
|
|
68
|
+
options_hash['correlation_id'] = correlation_id if correlation_id
|
|
69
|
+
|
|
70
|
+
# Build the exact fields that Rust TaskRequest expects
|
|
71
|
+
rust_hash = {
|
|
72
|
+
namespace: namespace,
|
|
73
|
+
name: name,
|
|
74
|
+
version: version,
|
|
75
|
+
status: status,
|
|
76
|
+
initiator: initiator,
|
|
77
|
+
source_system: source_system,
|
|
78
|
+
reason: reason,
|
|
79
|
+
complete: complete,
|
|
80
|
+
tags: tags,
|
|
81
|
+
context: context,
|
|
82
|
+
|
|
83
|
+
requested_at: requested_at&.utc&.strftime('%Y-%m-%dT%H:%M:%S'),
|
|
84
|
+
# New direct fields for distributed orchestration
|
|
85
|
+
priority: priority,
|
|
86
|
+
claim_timeout_seconds: claim_timeout_seconds
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Add options field only if there are options to add
|
|
90
|
+
rust_hash[:options] = options_hash unless options_hash.empty?
|
|
91
|
+
|
|
92
|
+
rust_hash
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Create from hash with proper type coercion
|
|
96
|
+
def self.from_hash(hash)
|
|
97
|
+
new(hash.transform_keys(&:to_sym))
|
|
98
|
+
rescue Dry::Struct::Error => e
|
|
99
|
+
raise TaskerCore::ValidationError, "Invalid TaskRequest: #{e.message}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Quick factory method for tests
|
|
103
|
+
def self.build_test(namespace:, name:, context:, **options)
|
|
104
|
+
from_hash({
|
|
105
|
+
namespace: namespace,
|
|
106
|
+
name: name,
|
|
107
|
+
context: context,
|
|
108
|
+
initiator: options[:initiator] || 'test',
|
|
109
|
+
source_system: options[:source_system] || 'rspec',
|
|
110
|
+
reason: options[:reason] || 'testing',
|
|
111
|
+
tags: options[:tags] || ['test'],
|
|
112
|
+
**options
|
|
113
|
+
})
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Pretty string representation
|
|
117
|
+
def to_s
|
|
118
|
+
"#<TaskRequest #{namespace}/#{name}:#{version} status=#{status} initiator=#{initiator}>"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def inspect
|
|
122
|
+
to_s
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Task response struct (for initialize_task results)
|
|
127
|
+
class TaskResponse < Dry::Struct
|
|
128
|
+
attribute :task_uuid, Types::String
|
|
129
|
+
attribute :status, Types::Coercible::String.enum(
|
|
130
|
+
'pending',
|
|
131
|
+
'in_progress',
|
|
132
|
+
'completed',
|
|
133
|
+
'failed',
|
|
134
|
+
'cancelled',
|
|
135
|
+
'paused'
|
|
136
|
+
)
|
|
137
|
+
attribute :workflow_steps, Types::Array.of(Types::Hash)
|
|
138
|
+
attribute? :created_at, Types::Constructor(Time)
|
|
139
|
+
attribute? :estimated_completion, Types::Constructor(Time)
|
|
140
|
+
attribute? :metadata, Types::Hash
|
|
141
|
+
|
|
142
|
+
def to_s
|
|
143
|
+
"#<TaskResponse task_uuid=#{task_uuid} status=#{status} steps=#{workflow_steps.size}>"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-struct'
|
|
4
|
+
require 'dry-types'
|
|
5
|
+
|
|
6
|
+
# Load all type modules
|
|
7
|
+
require_relative 'types/task_types'
|
|
8
|
+
require_relative 'types/step_types'
|
|
9
|
+
require_relative 'types/step_message'
|
|
10
|
+
require_relative 'types/simple_message' # NEW: simplified UUID-based messages
|
|
11
|
+
require_relative 'types/task_template' # TaskTemplate and StepTemplate types
|
|
12
|
+
require_relative 'types/step_handler_call_result' # NEW: standardized handler results
|
|
13
|
+
require_relative 'types/decision_point_outcome' # TAS-53: decision point outcomes
|
|
14
|
+
require_relative 'types/batch_processing_outcome' # TAS-59: batch processing outcomes
|
|
15
|
+
require_relative 'types/step_context' # TAS-96: cross-language standard step context
|
|
16
|
+
require_relative 'types/error_types' # TAS-96: cross-language standard error types
|
|
17
|
+
require_relative 'types/client_types' # TAS-231: client API response types
|
|
18
|
+
|
|
19
|
+
module TaskerCore
|
|
20
|
+
# Centralized type definitions for TaskerCore domain objects
|
|
21
|
+
#
|
|
22
|
+
# This module serves as the main entry point for all TaskerCore types,
|
|
23
|
+
# organizing them into logical groupings for better maintainability.
|
|
24
|
+
# All types are powered by dry-types for automatic validation, coercion,
|
|
25
|
+
# and schema enforcement.
|
|
26
|
+
#
|
|
27
|
+
# Type Categories:
|
|
28
|
+
# - **Task Types**: TaskRequest, TaskResponse for task creation and responses
|
|
29
|
+
# - **Step Types**: StepCompletion for step execution results
|
|
30
|
+
# - **Message Types**: SimpleStepMessage, SimpleTaskMessage for lightweight messaging
|
|
31
|
+
# - **Template Types**: TaskTemplate, StepTemplate for workflow definitions
|
|
32
|
+
# - **Result Types**: StepHandlerCallResult for standardized handler responses
|
|
33
|
+
#
|
|
34
|
+
# @example Using task types with validation
|
|
35
|
+
# # Build a task request with automatic validation
|
|
36
|
+
# request = TaskerCore::Types::TaskRequest.new(
|
|
37
|
+
# namespace: "fulfillment",
|
|
38
|
+
# name: "process_order",
|
|
39
|
+
# context: { order_id: "123", items: [...] }
|
|
40
|
+
# )
|
|
41
|
+
# # => Automatically validates required fields and types
|
|
42
|
+
#
|
|
43
|
+
# @example Type coercion and defaults
|
|
44
|
+
# # dry-types automatically coerces compatible types
|
|
45
|
+
# completion = TaskerCore::Types::StepCompletion.new(
|
|
46
|
+
# task_uuid: "123",
|
|
47
|
+
# step_uuid: "456",
|
|
48
|
+
# success: "true" # Automatically coerced to boolean true
|
|
49
|
+
# )
|
|
50
|
+
#
|
|
51
|
+
# @example Building test fixtures
|
|
52
|
+
# # Use build_test for quick test data generation
|
|
53
|
+
# request = TaskerCore::Types::TaskRequest.build_test(
|
|
54
|
+
# namespace: "fulfillment",
|
|
55
|
+
# name: "process_order",
|
|
56
|
+
# context: { order_id: "123" }
|
|
57
|
+
# )
|
|
58
|
+
# # => Creates valid request with sensible test defaults
|
|
59
|
+
#
|
|
60
|
+
# @example Using simple message types for UUID-based communication
|
|
61
|
+
# # Lightweight message for step execution
|
|
62
|
+
# simple_message = TaskerCore::Types::SimpleStepMessage.new(
|
|
63
|
+
# task_uuid: "550e8400-e29b-41d4-a716-446655440000",
|
|
64
|
+
# step_uuid: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
|
|
65
|
+
# ready_dependency_step_uuids: [
|
|
66
|
+
# "123e4567-e89b-12d3-a456-426614174000",
|
|
67
|
+
# "456e7890-e12b-34d5-a678-426614174001"
|
|
68
|
+
# ]
|
|
69
|
+
# )
|
|
70
|
+
#
|
|
71
|
+
# @example Using template types for workflow definitions
|
|
72
|
+
# # Define a task template
|
|
73
|
+
# template = TaskerCore::Types::TaskTemplate.new(
|
|
74
|
+
# namespace: "payments",
|
|
75
|
+
# name: "process_payment",
|
|
76
|
+
# steps: [
|
|
77
|
+
# { name: "validate", handler_class: "ValidatePaymentHandler" },
|
|
78
|
+
# { name: "charge", handler_class: "ChargePaymentHandler" }
|
|
79
|
+
# ]
|
|
80
|
+
# )
|
|
81
|
+
#
|
|
82
|
+
# @example Standardized handler results
|
|
83
|
+
# # Return structured results from handlers
|
|
84
|
+
# result = TaskerCore::Types::StepHandlerCallResult.success(
|
|
85
|
+
# result: { payment_id: "pay_123", amount: 100.00 },
|
|
86
|
+
# metadata: {
|
|
87
|
+
# processing_time_ms: 125,
|
|
88
|
+
# gateway: "stripe"
|
|
89
|
+
# }
|
|
90
|
+
# )
|
|
91
|
+
#
|
|
92
|
+
# Validation Benefits:
|
|
93
|
+
# - **Type Safety**: Automatic type checking and coercion
|
|
94
|
+
# - **Required Fields**: Ensures all required data is present
|
|
95
|
+
# - **Schema Enforcement**: Validates structure matches expectations
|
|
96
|
+
# - **Early Error Detection**: Catches data issues before processing
|
|
97
|
+
# - **Documentation**: Types serve as living documentation
|
|
98
|
+
#
|
|
99
|
+
# @see TaskerCore::Types::TaskRequest For task creation
|
|
100
|
+
# @see TaskerCore::Types::TaskResponse For task responses
|
|
101
|
+
# @see TaskerCore::Types::StepCompletion For step execution results
|
|
102
|
+
# @see TaskerCore::Types::SimpleStepMessage For UUID-based step messaging
|
|
103
|
+
# @see TaskerCore::Types::SimpleTaskMessage For UUID-based task messaging
|
|
104
|
+
# @see TaskerCore::Types::TaskTemplate For workflow definitions
|
|
105
|
+
# @see TaskerCore::Types::StepHandlerCallResult For standardized handler responses
|
|
106
|
+
# @see https://dry-rb.org/gems/dry-types For dry-types documentation
|
|
107
|
+
module Types
|
|
108
|
+
include Dry.Types()
|
|
109
|
+
|
|
110
|
+
# Make base types available in class scope
|
|
111
|
+
|
|
112
|
+
# Re-export all type classes for backward compatibility and convenience
|
|
113
|
+
|
|
114
|
+
# Task-related types
|
|
115
|
+
TaskRequest = TaskTypes::TaskRequest
|
|
116
|
+
TaskResponse = TaskTypes::TaskResponse
|
|
117
|
+
|
|
118
|
+
# Step-related types
|
|
119
|
+
StepCompletion = StepTypes::StepCompletion
|
|
120
|
+
|
|
121
|
+
# Simple message types (UUID-based) - already defined in the namespace
|
|
122
|
+
|
|
123
|
+
# Client API types (TAS-231)
|
|
124
|
+
ClientTaskResponse = ClientTypes::TaskResponse
|
|
125
|
+
ClientTaskListResponse = ClientTypes::TaskListResponse
|
|
126
|
+
ClientStepResponse = ClientTypes::StepResponse
|
|
127
|
+
ClientStepAuditResponse = ClientTypes::StepAuditResponse
|
|
128
|
+
ClientHealthResponse = ClientTypes::HealthResponse
|
|
129
|
+
ClientPaginationInfo = ClientTypes::PaginationInfo
|
|
130
|
+
ClientStepReadiness = ClientTypes::StepReadiness
|
|
131
|
+
end
|
|
132
|
+
end
|