cmdx 1.8.0 → 1.9.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 +4 -4
- data/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +3 -3
- data/.cursor/prompts/llms.md +1 -3
- data/.cursor/prompts/yardoc.md +1 -0
- data/.irbrc +14 -2
- data/CHANGELOG.md +64 -45
- data/LLM.md +159 -53
- data/README.md +26 -83
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +1 -0
- data/docs/attributes/coercions.md +12 -24
- data/docs/attributes/defaults.md +3 -16
- data/docs/attributes/definitions.md +16 -30
- data/docs/attributes/naming.md +3 -13
- data/docs/attributes/transformations.md +63 -0
- data/docs/attributes/validations.md +14 -33
- data/docs/basics/chain.md +14 -23
- data/docs/basics/context.md +13 -22
- data/docs/basics/execution.md +8 -26
- data/docs/basics/setup.md +8 -19
- data/docs/callbacks.md +19 -32
- data/docs/deprecation.md +8 -25
- data/docs/getting_started.md +109 -76
- data/docs/index.md +132 -0
- data/docs/internationalization.md +6 -18
- data/docs/interruptions/exceptions.md +10 -16
- data/docs/interruptions/faults.md +8 -25
- data/docs/interruptions/halt.md +12 -27
- data/docs/logging.md +7 -17
- data/docs/middlewares.md +13 -29
- data/docs/outcomes/result.md +21 -38
- data/docs/outcomes/states.md +8 -22
- data/docs/outcomes/statuses.md +10 -21
- data/docs/stylesheets/extra.css +42 -0
- data/docs/tips_and_tricks.md +7 -46
- data/docs/workflows.md +23 -38
- data/examples/active_record_query_tagging.md +46 -0
- data/examples/paper_trail_whatdunnit.md +39 -0
- data/lib/cmdx/attribute.rb +88 -6
- data/lib/cmdx/attribute_registry.rb +20 -0
- data/lib/cmdx/attribute_value.rb +56 -10
- data/lib/cmdx/callback_registry.rb +31 -2
- data/lib/cmdx/chain.rb +34 -1
- data/lib/cmdx/coercion_registry.rb +18 -0
- data/lib/cmdx/coercions/array.rb +2 -0
- data/lib/cmdx/coercions/big_decimal.rb +3 -0
- data/lib/cmdx/coercions/boolean.rb +5 -0
- data/lib/cmdx/coercions/complex.rb +2 -0
- data/lib/cmdx/coercions/date.rb +4 -0
- data/lib/cmdx/coercions/date_time.rb +5 -0
- data/lib/cmdx/coercions/float.rb +2 -0
- data/lib/cmdx/coercions/hash.rb +4 -0
- data/lib/cmdx/coercions/integer.rb +2 -0
- data/lib/cmdx/coercions/rational.rb +2 -0
- data/lib/cmdx/coercions/string.rb +2 -0
- data/lib/cmdx/coercions/symbol.rb +2 -0
- data/lib/cmdx/coercions/time.rb +5 -0
- data/lib/cmdx/configuration.rb +119 -3
- data/lib/cmdx/context.rb +36 -0
- data/lib/cmdx/deprecator.rb +6 -3
- data/lib/cmdx/errors.rb +22 -0
- data/lib/cmdx/executor.rb +136 -7
- data/lib/cmdx/faults.rb +14 -0
- data/lib/cmdx/identifier.rb +2 -0
- data/lib/cmdx/locale.rb +3 -0
- data/lib/cmdx/log_formatters/json.rb +2 -0
- data/lib/cmdx/log_formatters/key_value.rb +2 -0
- data/lib/cmdx/log_formatters/line.rb +2 -0
- data/lib/cmdx/log_formatters/logstash.rb +2 -0
- data/lib/cmdx/log_formatters/raw.rb +2 -0
- data/lib/cmdx/middleware_registry.rb +20 -0
- data/lib/cmdx/middlewares/correlate.rb +11 -0
- data/lib/cmdx/middlewares/runtime.rb +4 -0
- data/lib/cmdx/middlewares/timeout.rb +4 -0
- data/lib/cmdx/pipeline.rb +24 -5
- data/lib/cmdx/railtie.rb +13 -0
- data/lib/cmdx/result.rb +133 -2
- data/lib/cmdx/task.rb +103 -8
- data/lib/cmdx/utils/call.rb +2 -0
- data/lib/cmdx/utils/condition.rb +3 -0
- data/lib/cmdx/utils/format.rb +5 -0
- data/lib/cmdx/validator_registry.rb +18 -0
- data/lib/cmdx/validators/exclusion.rb +2 -0
- data/lib/cmdx/validators/format.rb +2 -0
- data/lib/cmdx/validators/inclusion.rb +2 -0
- data/lib/cmdx/validators/length.rb +14 -0
- data/lib/cmdx/validators/numeric.rb +14 -0
- data/lib/cmdx/validators/presence.rb +2 -0
- data/lib/cmdx/version.rb +4 -1
- data/lib/cmdx/workflow.rb +10 -0
- data/lib/cmdx.rb +9 -0
- data/lib/generators/cmdx/locale_generator.rb +0 -1
- data/lib/generators/cmdx/templates/install.rb +9 -0
- data/mkdocs.yml +122 -0
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +1 -0
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +1 -0
- metadata +14 -3
- data/lib/cmdx/freezer.rb +0 -51
- data/src/cmdx-logo.png +0 -0
data/lib/cmdx/result.rb
CHANGED
|
@@ -11,18 +11,22 @@ module CMDx
|
|
|
11
11
|
|
|
12
12
|
extend Forwardable
|
|
13
13
|
|
|
14
|
+
# @rbs STATES: Array[String]
|
|
14
15
|
STATES = [
|
|
15
16
|
INITIALIZED = "initialized", # Initial state before execution
|
|
16
17
|
EXECUTING = "executing", # Currently executing task logic
|
|
17
18
|
COMPLETE = "complete", # Successfully completed execution
|
|
18
19
|
INTERRUPTED = "interrupted" # Execution was halted due to failure
|
|
19
20
|
].freeze
|
|
21
|
+
|
|
22
|
+
# @rbs STATUSES: Array[String]
|
|
20
23
|
STATUSES = [
|
|
21
24
|
SUCCESS = "success", # Task completed successfully
|
|
22
25
|
SKIPPED = "skipped", # Task was skipped intentionally
|
|
23
26
|
FAILED = "failed" # Task failed due to error or validation
|
|
24
27
|
].freeze
|
|
25
28
|
|
|
29
|
+
# @rbs STRIP_FAILURE: Proc
|
|
26
30
|
STRIP_FAILURE = proc do |hash, result, key|
|
|
27
31
|
unless result.send(:"#{key}?")
|
|
28
32
|
# Strip caused/threw failures since its the same info as the log line
|
|
@@ -31,7 +35,65 @@ module CMDx
|
|
|
31
35
|
end.freeze
|
|
32
36
|
private_constant :STRIP_FAILURE
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
# Returns the task instance associated with this result.
|
|
39
|
+
#
|
|
40
|
+
# @return [CMDx::Task] The task instance
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# result.task.id # => "users/create"
|
|
44
|
+
#
|
|
45
|
+
# @rbs @task: Task
|
|
46
|
+
attr_reader :task
|
|
47
|
+
|
|
48
|
+
# Returns the current execution state of the result.
|
|
49
|
+
#
|
|
50
|
+
# @return [String] One of: "initialized", "executing", "complete", "interrupted"
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# result.state # => "complete"
|
|
54
|
+
#
|
|
55
|
+
# @rbs @state: String
|
|
56
|
+
attr_reader :state
|
|
57
|
+
|
|
58
|
+
# Returns the execution status of the result.
|
|
59
|
+
#
|
|
60
|
+
# @return [String] One of: "success", "skipped", "failed"
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# result.status # => "success"
|
|
64
|
+
#
|
|
65
|
+
# @rbs @status: String
|
|
66
|
+
attr_reader :status
|
|
67
|
+
|
|
68
|
+
# Returns additional metadata about the result.
|
|
69
|
+
#
|
|
70
|
+
# @return [Hash{Symbol => Object}] Metadata hash
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# result.metadata # => { duration: 1.5, retries: 2 }
|
|
74
|
+
#
|
|
75
|
+
# @rbs @metadata: Hash[Symbol, untyped]
|
|
76
|
+
attr_reader :metadata
|
|
77
|
+
|
|
78
|
+
# Returns the reason for interruption (skip or failure).
|
|
79
|
+
#
|
|
80
|
+
# @return [String, nil] The reason message, or nil if not interrupted
|
|
81
|
+
#
|
|
82
|
+
# @example
|
|
83
|
+
# result.reason # => "Validation failed"
|
|
84
|
+
#
|
|
85
|
+
# @rbs @reason: (String | nil)
|
|
86
|
+
attr_reader :reason
|
|
87
|
+
|
|
88
|
+
# Returns the exception that caused the interruption.
|
|
89
|
+
#
|
|
90
|
+
# @return [Exception, nil] The causing exception, or nil if not interrupted
|
|
91
|
+
#
|
|
92
|
+
# @example
|
|
93
|
+
# result.cause # => #<StandardError: Connection timeout>
|
|
94
|
+
#
|
|
95
|
+
# @rbs @cause: (Exception | nil)
|
|
96
|
+
attr_reader :cause
|
|
35
97
|
|
|
36
98
|
def_delegators :task, :context, :chain, :errors
|
|
37
99
|
alias ctx context
|
|
@@ -45,6 +107,8 @@ module CMDx
|
|
|
45
107
|
# @example
|
|
46
108
|
# result = CMDx::Result.new(my_task)
|
|
47
109
|
# result.state # => "initialized"
|
|
110
|
+
#
|
|
111
|
+
# @rbs (Task) -> void
|
|
48
112
|
def initialize(task)
|
|
49
113
|
raise TypeError, "must be a CMDx::Task" unless task.is_a?(CMDx::Task)
|
|
50
114
|
|
|
@@ -62,6 +126,8 @@ module CMDx
|
|
|
62
126
|
# @example
|
|
63
127
|
# result.initialized? # => true
|
|
64
128
|
# result.executing? # => false
|
|
129
|
+
#
|
|
130
|
+
# @rbs () -> bool
|
|
65
131
|
define_method(:"#{s}?") { state == s }
|
|
66
132
|
|
|
67
133
|
# @param block [Proc] Block to execute conditionally
|
|
@@ -75,6 +141,8 @@ module CMDx
|
|
|
75
141
|
# @example
|
|
76
142
|
# result.handle_initialized { |r| puts "Starting execution" }
|
|
77
143
|
# result.handle_complete { |r| puts "Task completed" }
|
|
144
|
+
#
|
|
145
|
+
# @rbs () { (Result) -> void } -> self
|
|
78
146
|
define_method(:"handle_#{s}") do |&block|
|
|
79
147
|
raise ArgumentError, "block required" unless block
|
|
80
148
|
|
|
@@ -87,6 +155,8 @@ module CMDx
|
|
|
87
155
|
#
|
|
88
156
|
# @example
|
|
89
157
|
# result.executed! # Transitions to complete or interrupted
|
|
158
|
+
#
|
|
159
|
+
# @rbs () -> self
|
|
90
160
|
def executed!
|
|
91
161
|
success? ? complete! : interrupt!
|
|
92
162
|
end
|
|
@@ -95,6 +165,8 @@ module CMDx
|
|
|
95
165
|
#
|
|
96
166
|
# @example
|
|
97
167
|
# result.executed? # => true if complete? || interrupted?
|
|
168
|
+
#
|
|
169
|
+
# @rbs () -> bool
|
|
98
170
|
def executed?
|
|
99
171
|
complete? || interrupted?
|
|
100
172
|
end
|
|
@@ -109,6 +181,8 @@ module CMDx
|
|
|
109
181
|
#
|
|
110
182
|
# @example
|
|
111
183
|
# result.handle_executed { |r| puts "Task finished: #{r.outcome}" }
|
|
184
|
+
#
|
|
185
|
+
# @rbs () { (Result) -> void } -> self
|
|
112
186
|
def handle_executed(&)
|
|
113
187
|
raise ArgumentError, "block required" unless block_given?
|
|
114
188
|
|
|
@@ -120,6 +194,8 @@ module CMDx
|
|
|
120
194
|
#
|
|
121
195
|
# @example
|
|
122
196
|
# result.executing! # Transitions from initialized to executing
|
|
197
|
+
#
|
|
198
|
+
# @rbs () -> void
|
|
123
199
|
def executing!
|
|
124
200
|
return if executing?
|
|
125
201
|
|
|
@@ -132,6 +208,8 @@ module CMDx
|
|
|
132
208
|
#
|
|
133
209
|
# @example
|
|
134
210
|
# result.complete! # Transitions from executing to complete
|
|
211
|
+
#
|
|
212
|
+
# @rbs () -> void
|
|
135
213
|
def complete!
|
|
136
214
|
return if complete?
|
|
137
215
|
|
|
@@ -144,6 +222,8 @@ module CMDx
|
|
|
144
222
|
#
|
|
145
223
|
# @example
|
|
146
224
|
# result.interrupt! # Transitions from executing to interrupted
|
|
225
|
+
#
|
|
226
|
+
# @rbs () -> void
|
|
147
227
|
def interrupt!
|
|
148
228
|
return if interrupted?
|
|
149
229
|
|
|
@@ -158,6 +238,8 @@ module CMDx
|
|
|
158
238
|
# @example
|
|
159
239
|
# result.success? # => true
|
|
160
240
|
# result.failed? # => false
|
|
241
|
+
#
|
|
242
|
+
# @rbs () -> bool
|
|
161
243
|
define_method(:"#{s}?") { status == s }
|
|
162
244
|
|
|
163
245
|
# @param block [Proc] Block to execute conditionally
|
|
@@ -171,6 +253,8 @@ module CMDx
|
|
|
171
253
|
# @example
|
|
172
254
|
# result.handle_success { |r| puts "Task succeeded" }
|
|
173
255
|
# result.handle_failed { |r| puts "Task failed: #{r.reason}" }
|
|
256
|
+
#
|
|
257
|
+
# @rbs () { (Result) -> void } -> self
|
|
174
258
|
define_method(:"handle_#{s}") do |&block|
|
|
175
259
|
raise ArgumentError, "block required" unless block
|
|
176
260
|
|
|
@@ -183,6 +267,8 @@ module CMDx
|
|
|
183
267
|
#
|
|
184
268
|
# @example
|
|
185
269
|
# result.good? # => true if !failed?
|
|
270
|
+
#
|
|
271
|
+
# @rbs () -> bool
|
|
186
272
|
def good?
|
|
187
273
|
!failed?
|
|
188
274
|
end
|
|
@@ -198,6 +284,8 @@ module CMDx
|
|
|
198
284
|
#
|
|
199
285
|
# @example
|
|
200
286
|
# result.handle_good { |r| puts "Task completed successfully" }
|
|
287
|
+
#
|
|
288
|
+
# @rbs () { (Result) -> void } -> self
|
|
201
289
|
def handle_good(&)
|
|
202
290
|
raise ArgumentError, "block required" unless block_given?
|
|
203
291
|
|
|
@@ -209,6 +297,8 @@ module CMDx
|
|
|
209
297
|
#
|
|
210
298
|
# @example
|
|
211
299
|
# result.bad? # => true if !success?
|
|
300
|
+
#
|
|
301
|
+
# @rbs () -> bool
|
|
212
302
|
def bad?
|
|
213
303
|
!success?
|
|
214
304
|
end
|
|
@@ -223,6 +313,8 @@ module CMDx
|
|
|
223
313
|
#
|
|
224
314
|
# @example
|
|
225
315
|
# result.handle_bad { |r| puts "Task had issues: #{r.reason}" }
|
|
316
|
+
#
|
|
317
|
+
# @rbs () { (Result) -> void } -> self
|
|
226
318
|
def handle_bad(&)
|
|
227
319
|
raise ArgumentError, "block required" unless block_given?
|
|
228
320
|
|
|
@@ -240,6 +332,8 @@ module CMDx
|
|
|
240
332
|
# @example
|
|
241
333
|
# result.skip!("Dependencies not met", cause: dependency_error)
|
|
242
334
|
# result.skip!("Already processed", halt: false)
|
|
335
|
+
#
|
|
336
|
+
# @rbs (?String? reason, halt: bool, cause: Exception?, **untyped metadata) -> void
|
|
243
337
|
def skip!(reason = nil, halt: true, cause: nil, **metadata)
|
|
244
338
|
return if skipped?
|
|
245
339
|
|
|
@@ -264,6 +358,8 @@ module CMDx
|
|
|
264
358
|
# @example
|
|
265
359
|
# result.fail!("Validation failed", cause: validation_error)
|
|
266
360
|
# result.fail!("Network timeout", halt: false, timeout: 30)
|
|
361
|
+
#
|
|
362
|
+
# @rbs (?String? reason, halt: bool, cause: Exception?, **untyped metadata) -> void
|
|
267
363
|
def fail!(reason = nil, halt: true, cause: nil, **metadata)
|
|
268
364
|
return if failed?
|
|
269
365
|
|
|
@@ -283,6 +379,8 @@ module CMDx
|
|
|
283
379
|
#
|
|
284
380
|
# @example
|
|
285
381
|
# result.halt! # Raises appropriate fault based on status
|
|
382
|
+
#
|
|
383
|
+
# @rbs () -> void
|
|
286
384
|
def halt!
|
|
287
385
|
return if success?
|
|
288
386
|
|
|
@@ -291,7 +389,16 @@ module CMDx
|
|
|
291
389
|
|
|
292
390
|
# Strip the first two frames (this method and the delegator)
|
|
293
391
|
frames = caller_locations(3..-1)
|
|
294
|
-
|
|
392
|
+
|
|
393
|
+
unless frames.empty?
|
|
394
|
+
frames = frames.map(&:to_s)
|
|
395
|
+
|
|
396
|
+
if (cleaner = task.class.settings[:backtrace_cleaner])
|
|
397
|
+
cleaner.call(frames)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
fault.set_backtrace(frames)
|
|
401
|
+
end
|
|
295
402
|
|
|
296
403
|
raise(fault)
|
|
297
404
|
end
|
|
@@ -306,6 +413,8 @@ module CMDx
|
|
|
306
413
|
# @example
|
|
307
414
|
# other_result = OtherTask.execute
|
|
308
415
|
# result.throw!(other_result, cause: upstream_error)
|
|
416
|
+
#
|
|
417
|
+
# @rbs (Result result, halt: bool, cause: Exception?, **untyped metadata) -> void
|
|
309
418
|
def throw!(result, halt: true, cause: nil, **metadata)
|
|
310
419
|
raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
|
|
311
420
|
|
|
@@ -323,6 +432,8 @@ module CMDx
|
|
|
323
432
|
# @example
|
|
324
433
|
# cause = result.caused_failure
|
|
325
434
|
# puts "Caused by: #{cause.task.id}" if cause
|
|
435
|
+
#
|
|
436
|
+
# @rbs () -> Result?
|
|
326
437
|
def caused_failure
|
|
327
438
|
return unless failed?
|
|
328
439
|
|
|
@@ -335,6 +446,8 @@ module CMDx
|
|
|
335
446
|
# if result.caused_failure?
|
|
336
447
|
# puts "This task caused the failure"
|
|
337
448
|
# end
|
|
449
|
+
#
|
|
450
|
+
# @rbs () -> bool
|
|
338
451
|
def caused_failure?
|
|
339
452
|
return false unless failed?
|
|
340
453
|
|
|
@@ -346,6 +459,8 @@ module CMDx
|
|
|
346
459
|
# @example
|
|
347
460
|
# thrown = result.threw_failure
|
|
348
461
|
# puts "Thrown by: #{thrown.task.id}" if thrown
|
|
462
|
+
#
|
|
463
|
+
# @rbs () -> Result?
|
|
349
464
|
def threw_failure
|
|
350
465
|
return unless failed?
|
|
351
466
|
|
|
@@ -360,6 +475,8 @@ module CMDx
|
|
|
360
475
|
# if result.threw_failure?
|
|
361
476
|
# puts "This task threw the failure"
|
|
362
477
|
# end
|
|
478
|
+
#
|
|
479
|
+
# @rbs () -> bool
|
|
363
480
|
def threw_failure?
|
|
364
481
|
return false unless failed?
|
|
365
482
|
|
|
@@ -372,6 +489,8 @@ module CMDx
|
|
|
372
489
|
# if result.thrown_failure?
|
|
373
490
|
# puts "This failure was thrown from another task"
|
|
374
491
|
# end
|
|
492
|
+
#
|
|
493
|
+
# @rbs () -> bool
|
|
375
494
|
def thrown_failure?
|
|
376
495
|
failed? && !caused_failure?
|
|
377
496
|
end
|
|
@@ -381,6 +500,8 @@ module CMDx
|
|
|
381
500
|
# @example
|
|
382
501
|
# position = result.index
|
|
383
502
|
# puts "Task #{position + 1} of #{chain.results.count}"
|
|
503
|
+
#
|
|
504
|
+
# @rbs () -> Integer
|
|
384
505
|
def index
|
|
385
506
|
chain.index(self)
|
|
386
507
|
end
|
|
@@ -389,6 +510,8 @@ module CMDx
|
|
|
389
510
|
#
|
|
390
511
|
# @example
|
|
391
512
|
# result.outcome # => "success" or "interrupted"
|
|
513
|
+
#
|
|
514
|
+
# @rbs () -> String
|
|
392
515
|
def outcome
|
|
393
516
|
initialized? || thrown_failure? ? state : status
|
|
394
517
|
end
|
|
@@ -398,6 +521,8 @@ module CMDx
|
|
|
398
521
|
# @example
|
|
399
522
|
# result.to_h
|
|
400
523
|
# # => {state: "complete", status: "success", outcome: "success", metadata: {}}
|
|
524
|
+
#
|
|
525
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
401
526
|
def to_h
|
|
402
527
|
task.to_h.merge!(
|
|
403
528
|
state:,
|
|
@@ -421,6 +546,8 @@ module CMDx
|
|
|
421
546
|
#
|
|
422
547
|
# @example
|
|
423
548
|
# result.to_s # => "task_id=my_task state=complete status=success"
|
|
549
|
+
#
|
|
550
|
+
# @rbs () -> String
|
|
424
551
|
def to_s
|
|
425
552
|
Utils::Format.to_str(to_h) do |key, value|
|
|
426
553
|
case key
|
|
@@ -437,6 +564,8 @@ module CMDx
|
|
|
437
564
|
# @example
|
|
438
565
|
# state, status = result.deconstruct
|
|
439
566
|
# puts "State: #{state}, Status: #{status}"
|
|
567
|
+
#
|
|
568
|
+
# @rbs (*untyped) -> Array[untyped]
|
|
440
569
|
def deconstruct(*)
|
|
441
570
|
[state, status, reason, cause, metadata]
|
|
442
571
|
end
|
|
@@ -452,6 +581,8 @@ module CMDx
|
|
|
452
581
|
# in {bad: true}
|
|
453
582
|
# puts "Task had issues"
|
|
454
583
|
# end
|
|
584
|
+
#
|
|
585
|
+
# @rbs (*untyped) -> Hash[Symbol, untyped]
|
|
455
586
|
def deconstruct_keys(*)
|
|
456
587
|
{
|
|
457
588
|
state: state,
|
data/lib/cmdx/task.rb
CHANGED
|
@@ -8,10 +8,68 @@ module CMDx
|
|
|
8
8
|
|
|
9
9
|
extend Forwardable
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Returns the hash of processed attribute values for this task.
|
|
12
|
+
#
|
|
13
|
+
# @return [Hash{Symbol => Object}] Hash of attribute names to their values
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# task.attributes # => { user_id: 42, user_name: "John" }
|
|
17
|
+
#
|
|
18
|
+
# @rbs @attributes: Hash[Symbol, untyped]
|
|
19
|
+
attr_reader :attributes
|
|
20
|
+
|
|
21
|
+
# Returns the collection of validation and execution errors.
|
|
22
|
+
#
|
|
23
|
+
# @return [Errors] The errors collection
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# task.errors.to_h # => { email: ["must be valid"] }
|
|
27
|
+
#
|
|
28
|
+
# @rbs @errors: Errors
|
|
29
|
+
attr_reader :errors
|
|
30
|
+
|
|
31
|
+
# Returns the unique identifier for this task instance.
|
|
32
|
+
#
|
|
33
|
+
# @return [String] The task identifier
|
|
34
|
+
#
|
|
35
|
+
# @example
|
|
36
|
+
# task.id # => "abc123xyz"
|
|
37
|
+
#
|
|
38
|
+
# @rbs @id: String
|
|
39
|
+
attr_reader :id
|
|
40
|
+
|
|
41
|
+
# Returns the execution context for this task.
|
|
42
|
+
#
|
|
43
|
+
# @return [Context] The context instance
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# task.context[:user_id] # => 42
|
|
47
|
+
#
|
|
48
|
+
# @rbs @context: Context
|
|
49
|
+
attr_reader :context
|
|
12
50
|
alias ctx context
|
|
51
|
+
|
|
52
|
+
# Returns the execution result for this task.
|
|
53
|
+
#
|
|
54
|
+
# @return [Result] The result instance
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# task.result.status # => "success"
|
|
58
|
+
#
|
|
59
|
+
# @rbs @result: Result
|
|
60
|
+
attr_reader :result
|
|
13
61
|
alias res result
|
|
14
62
|
|
|
63
|
+
# Returns the execution chain containing all task results.
|
|
64
|
+
#
|
|
65
|
+
# @return [Chain] The chain instance
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# task.chain.results.size # => 3
|
|
69
|
+
#
|
|
70
|
+
# @rbs @chain: Chain
|
|
71
|
+
attr_reader :chain
|
|
72
|
+
|
|
15
73
|
def_delegators :result, :skip!, :fail!, :throw!
|
|
16
74
|
|
|
17
75
|
# @param context [Hash, Context] The initial context for the task
|
|
@@ -25,6 +83,8 @@ module CMDx
|
|
|
25
83
|
# @example
|
|
26
84
|
# task = MyTask.new(name: "example", priority: :high)
|
|
27
85
|
# task = MyTask.new(Context.build(name: "example"))
|
|
86
|
+
#
|
|
87
|
+
# @rbs (untyped context) -> void
|
|
28
88
|
def initialize(context = {})
|
|
29
89
|
Deprecator.restrict(self)
|
|
30
90
|
|
|
@@ -40,9 +100,6 @@ module CMDx
|
|
|
40
100
|
class << self
|
|
41
101
|
|
|
42
102
|
# @param options [Hash] Configuration options to merge with existing settings
|
|
43
|
-
# @option options [AttributeRegistry] :attributes Registry for task attributes
|
|
44
|
-
# @option options [Boolean] :deprecate Whether the task is deprecated
|
|
45
|
-
# @option options [Array<Symbol>] :tags Tags associated with the task
|
|
46
103
|
#
|
|
47
104
|
# @return [Hash] The merged settings hash
|
|
48
105
|
#
|
|
@@ -50,17 +107,27 @@ module CMDx
|
|
|
50
107
|
# class MyTask < Task
|
|
51
108
|
# settings deprecate: true, tags: [:experimental]
|
|
52
109
|
# end
|
|
110
|
+
#
|
|
111
|
+
# @rbs (**untyped options) -> Hash[Symbol, untyped]
|
|
53
112
|
def settings(**options)
|
|
54
113
|
@settings ||= begin
|
|
55
114
|
hash =
|
|
56
115
|
if superclass.respond_to?(:settings)
|
|
57
|
-
superclass.settings
|
|
116
|
+
parent = superclass.settings
|
|
117
|
+
parent
|
|
118
|
+
.except(:backtrace_cleaner, :exception_handler, :logger, :deprecate)
|
|
119
|
+
.transform_values!(&:dup)
|
|
120
|
+
.merge!(
|
|
121
|
+
backtrace_cleaner: parent[:backtrace_cleaner] || CMDx.configuration.backtrace_cleaner,
|
|
122
|
+
exception_handler: parent[:exception_handler] || CMDx.configuration.exception_handler,
|
|
123
|
+
logger: parent[:logger] || CMDx.configuration.logger,
|
|
124
|
+
deprecate: parent[:deprecate]
|
|
125
|
+
)
|
|
58
126
|
else
|
|
59
|
-
CMDx.configuration.to_h
|
|
60
|
-
end
|
|
127
|
+
CMDx.configuration.to_h
|
|
128
|
+
end
|
|
61
129
|
|
|
62
130
|
hash[:attributes] ||= AttributeRegistry.new
|
|
63
|
-
hash[:deprecate] ||= false
|
|
64
131
|
hash[:tags] ||= []
|
|
65
132
|
|
|
66
133
|
hash.merge!(options)
|
|
@@ -76,6 +143,8 @@ module CMDx
|
|
|
76
143
|
# @example
|
|
77
144
|
# register(:attribute, MyAttribute.new)
|
|
78
145
|
# register(:callback, :before, -> { puts "before" })
|
|
146
|
+
#
|
|
147
|
+
# @rbs (Symbol type, untyped object, *untyped) -> void
|
|
79
148
|
def register(type, object, ...)
|
|
80
149
|
case type
|
|
81
150
|
when :attribute then settings[:attributes].register(object, ...)
|
|
@@ -96,6 +165,8 @@ module CMDx
|
|
|
96
165
|
# @example
|
|
97
166
|
# deregister(:attribute, :name)
|
|
98
167
|
# deregister(:callback, :before, MyCallback)
|
|
168
|
+
#
|
|
169
|
+
# @rbs (Symbol type, untyped object, *untyped) -> void
|
|
99
170
|
def deregister(type, object, ...)
|
|
100
171
|
case type
|
|
101
172
|
when :attribute then settings[:attributes].deregister(object, ...)
|
|
@@ -112,6 +183,8 @@ module CMDx
|
|
|
112
183
|
# @example
|
|
113
184
|
# attributes :name, :email
|
|
114
185
|
# attributes :age, type: Integer, default: 18
|
|
186
|
+
#
|
|
187
|
+
# @rbs (*untyped) -> void
|
|
115
188
|
def attributes(...)
|
|
116
189
|
register(:attribute, Attribute.build(...))
|
|
117
190
|
end
|
|
@@ -122,6 +195,8 @@ module CMDx
|
|
|
122
195
|
# @example
|
|
123
196
|
# optional :description, :notes
|
|
124
197
|
# optional :priority, type: Symbol, default: :normal
|
|
198
|
+
#
|
|
199
|
+
# @rbs (*untyped) -> void
|
|
125
200
|
def optional(...)
|
|
126
201
|
register(:attribute, Attribute.optional(...))
|
|
127
202
|
end
|
|
@@ -131,6 +206,8 @@ module CMDx
|
|
|
131
206
|
# @example
|
|
132
207
|
# required :name, :email
|
|
133
208
|
# required :age, type: Integer, min: 0
|
|
209
|
+
#
|
|
210
|
+
# @rbs (*untyped) -> void
|
|
134
211
|
def required(...)
|
|
135
212
|
register(:attribute, Attribute.required(...))
|
|
136
213
|
end
|
|
@@ -139,6 +216,8 @@ module CMDx
|
|
|
139
216
|
#
|
|
140
217
|
# @example
|
|
141
218
|
# remove_attributes :old_field, :deprecated_field
|
|
219
|
+
#
|
|
220
|
+
# @rbs (*Symbol names) -> void
|
|
142
221
|
def remove_attributes(*names)
|
|
143
222
|
deregister(:attribute, names)
|
|
144
223
|
end
|
|
@@ -155,6 +234,8 @@ module CMDx
|
|
|
155
234
|
# before { puts "before execution" }
|
|
156
235
|
# after :cleanup, priority: :high
|
|
157
236
|
# around ->(task) { task.logger.info("starting") }
|
|
237
|
+
#
|
|
238
|
+
# @rbs (*untyped callables, **untyped options) ?{ () -> void } -> void
|
|
158
239
|
define_method(callback) do |*callables, **options, &block|
|
|
159
240
|
register(:callback, callback, *callables, **options, &block)
|
|
160
241
|
end
|
|
@@ -169,6 +250,8 @@ module CMDx
|
|
|
169
250
|
# if result.success?
|
|
170
251
|
# puts "Task completed successfully"
|
|
171
252
|
# end
|
|
253
|
+
#
|
|
254
|
+
# @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
|
|
172
255
|
def execute(*args, **kwargs)
|
|
173
256
|
task = new(*args, **kwargs)
|
|
174
257
|
task.execute(raise: false)
|
|
@@ -184,6 +267,8 @@ module CMDx
|
|
|
184
267
|
# @example
|
|
185
268
|
# result = MyTask.execute!(name: "example")
|
|
186
269
|
# # Will raise an exception if execution fails
|
|
270
|
+
#
|
|
271
|
+
# @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
|
|
187
272
|
def execute!(*args, **kwargs)
|
|
188
273
|
task = new(*args, **kwargs)
|
|
189
274
|
task.execute(raise: true)
|
|
@@ -199,6 +284,8 @@ module CMDx
|
|
|
199
284
|
# @example
|
|
200
285
|
# result = task.execute
|
|
201
286
|
# result = task.execute(raise: true)
|
|
287
|
+
#
|
|
288
|
+
# @rbs (raise: bool) ?{ (Result) -> void } -> Result
|
|
202
289
|
def execute(raise: false)
|
|
203
290
|
Executor.execute(self, raise:)
|
|
204
291
|
block_given? ? yield(result) : result
|
|
@@ -213,6 +300,8 @@ module CMDx
|
|
|
213
300
|
# puts "Performing work..."
|
|
214
301
|
# end
|
|
215
302
|
# end
|
|
303
|
+
#
|
|
304
|
+
# @rbs () -> void
|
|
216
305
|
def work
|
|
217
306
|
raise UndefinedMethodError, "undefined method #{self.class.name}#work"
|
|
218
307
|
end
|
|
@@ -222,6 +311,8 @@ module CMDx
|
|
|
222
311
|
# @example
|
|
223
312
|
# logger.info "Starting task execution"
|
|
224
313
|
# logger.error "Task failed", error: exception
|
|
314
|
+
#
|
|
315
|
+
# @rbs () -> Logger
|
|
225
316
|
def logger
|
|
226
317
|
@logger ||= begin
|
|
227
318
|
logger = self.class.settings[:logger] || CMDx.configuration.logger
|
|
@@ -244,6 +335,8 @@ module CMDx
|
|
|
244
335
|
# task_hash = task.to_h
|
|
245
336
|
# puts "Task type: #{task_hash[:type]}"
|
|
246
337
|
# puts "Task tags: #{task_hash[:tags].join(', ')}"
|
|
338
|
+
#
|
|
339
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
247
340
|
def to_h
|
|
248
341
|
{
|
|
249
342
|
index: result.index,
|
|
@@ -260,6 +353,8 @@ module CMDx
|
|
|
260
353
|
# @example
|
|
261
354
|
# puts task.to_s
|
|
262
355
|
# # Output: "Task[MyTask] tags: [:important] id: abc123"
|
|
356
|
+
#
|
|
357
|
+
# @rbs () -> String
|
|
263
358
|
def to_s
|
|
264
359
|
Utils::Format.to_str(to_h)
|
|
265
360
|
end
|
data/lib/cmdx/utils/call.rb
CHANGED
|
@@ -32,6 +32,8 @@ module CMDx
|
|
|
32
32
|
# @example Invoking a callable object
|
|
33
33
|
# callable = MyCallable.new
|
|
34
34
|
# Call.invoke(user, callable, 'data')
|
|
35
|
+
#
|
|
36
|
+
# @rbs (untyped target, (Symbol | Proc | untyped) callable, *untyped args, **untyped kwargs) ?{ () -> untyped } -> untyped
|
|
35
37
|
def invoke(target, callable, *args, **kwargs, &)
|
|
36
38
|
if callable.is_a?(Symbol)
|
|
37
39
|
target.send(callable, *args, **kwargs, &)
|
data/lib/cmdx/utils/condition.rb
CHANGED
|
@@ -11,6 +11,7 @@ module CMDx
|
|
|
11
11
|
|
|
12
12
|
extend self
|
|
13
13
|
|
|
14
|
+
# @rbs EVAL: Proc
|
|
14
15
|
EVAL = proc do |target, callable, *args, **kwargs, &block|
|
|
15
16
|
case callable
|
|
16
17
|
when NilClass, FalseClass, TrueClass then !!callable
|
|
@@ -53,6 +54,8 @@ module CMDx
|
|
|
53
54
|
# @example With arguments and block
|
|
54
55
|
# Condition.evaluate(user, if: ->(u) { u.has_permission?(:admin) }, :admin)
|
|
55
56
|
# # => true if the proc returns true when called with user and :admin
|
|
57
|
+
#
|
|
58
|
+
# @rbs (untyped target, Hash[Symbol, untyped] options, *untyped) ?{ () -> untyped } -> bool
|
|
56
59
|
def evaluate(target, options, ...)
|
|
57
60
|
case options
|
|
58
61
|
in if: if_cond, unless: unless_cond
|
data/lib/cmdx/utils/format.rb
CHANGED
|
@@ -8,6 +8,7 @@ module CMDx
|
|
|
8
8
|
|
|
9
9
|
extend self
|
|
10
10
|
|
|
11
|
+
# @rbs FORMATTER: Proc
|
|
11
12
|
FORMATTER = proc do |key, value|
|
|
12
13
|
"#{key}=#{value.inspect}"
|
|
13
14
|
end.freeze
|
|
@@ -28,6 +29,8 @@ module CMDx
|
|
|
28
29
|
# @example CMDx object
|
|
29
30
|
# Format.to_log(CMDx::Task.new(name: "task1"))
|
|
30
31
|
# # => {name: "task1"}
|
|
32
|
+
#
|
|
33
|
+
# @rbs (untyped message) -> untyped
|
|
31
34
|
def to_log(message)
|
|
32
35
|
if message.respond_to?(:to_h) && message.class.ancestors.any? { |a| a.to_s.start_with?("CMDx") }
|
|
33
36
|
message.to_h
|
|
@@ -51,6 +54,8 @@ module CMDx
|
|
|
51
54
|
# @example Custom formatter
|
|
52
55
|
# Format.to_str({count: 5, total: 100}) { |k, v| "#{k}:#{v}" }
|
|
53
56
|
# # => "count:5 total:100"
|
|
57
|
+
#
|
|
58
|
+
# @rbs (Hash[untyped, untyped] hash) ?{ (untyped, untyped) -> String } -> String
|
|
54
59
|
def to_str(hash, &block)
|
|
55
60
|
block ||= FORMATTER
|
|
56
61
|
hash.map(&block).join(" ")
|