cmdx 1.13.0 → 1.14.0
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/CHANGELOG.md +84 -76
- data/LICENSE.txt +3 -20
- data/README.md +8 -7
- data/lib/cmdx/attribute.rb +21 -5
- data/lib/cmdx/context.rb +16 -0
- data/lib/cmdx/executor.rb +9 -9
- data/lib/cmdx/result.rb +27 -7
- data/lib/cmdx/task.rb +19 -0
- data/lib/cmdx/version.rb +1 -1
- data/mkdocs.yml +62 -36
- metadata +3 -57
- data/.cursor/prompts/docs.md +0 -12
- data/.cursor/prompts/llms.md +0 -8
- data/.cursor/prompts/rspec.md +0 -24
- data/.cursor/prompts/yardoc.md +0 -15
- data/.cursor/rules/cursor-instructions.mdc +0 -68
- data/.irbrc +0 -18
- data/.rspec +0 -4
- data/.rubocop.yml +0 -95
- data/.ruby-version +0 -1
- data/.yard-lint.yml +0 -174
- data/.yardopts +0 -7
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +0 -1
- data/docs/attributes/coercions.md +0 -155
- data/docs/attributes/defaults.md +0 -77
- data/docs/attributes/definitions.md +0 -283
- data/docs/attributes/naming.md +0 -68
- data/docs/attributes/transformations.md +0 -63
- data/docs/attributes/validations.md +0 -336
- data/docs/basics/chain.md +0 -108
- data/docs/basics/context.md +0 -121
- data/docs/basics/execution.md +0 -152
- data/docs/basics/setup.md +0 -107
- data/docs/callbacks.md +0 -157
- data/docs/configuration.md +0 -314
- data/docs/deprecation.md +0 -143
- data/docs/getting_started.md +0 -137
- data/docs/index.md +0 -134
- data/docs/internationalization.md +0 -126
- data/docs/interruptions/exceptions.md +0 -52
- data/docs/interruptions/faults.md +0 -169
- data/docs/interruptions/halt.md +0 -216
- data/docs/logging.md +0 -90
- data/docs/middlewares.md +0 -191
- data/docs/outcomes/result.md +0 -197
- data/docs/outcomes/states.md +0 -66
- data/docs/outcomes/statuses.md +0 -65
- data/docs/retries.md +0 -121
- data/docs/stylesheets/extra.css +0 -42
- data/docs/tips_and_tricks.md +0 -157
- data/docs/workflows.md +0 -226
- data/examples/active_record_database_transaction.md +0 -27
- data/examples/active_record_query_tagging.md +0 -46
- data/examples/flipper_feature_flags.md +0 -50
- data/examples/paper_trail_whatdunnit.md +0 -39
- data/examples/redis_idempotency.md +0 -71
- data/examples/sentry_error_tracking.md +0 -46
- data/examples/sidekiq_async_execution.md +0 -29
- data/examples/stoplight_circuit_breaker.md +0 -36
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +0 -1
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +0 -1
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Active Record Query Tagging
|
|
2
|
-
|
|
3
|
-
Add a comment to every query indicating some context to help you track down where that query came from, eg:
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
/*cmdx_task_class:ExportReportTask,cmdx_chain_id:018c2b95-b764-7615*/ SELECT * FROM reports WHERE id = 1
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
### Setup
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
# config/application.rb
|
|
13
|
-
config.active_record.query_log_tags_enabled = true
|
|
14
|
-
config.active_record.query_log_tags += [
|
|
15
|
-
:cmdx_correlation_id,
|
|
16
|
-
:cmdx_chain_id,
|
|
17
|
-
:cmdx_task_class,
|
|
18
|
-
:cmdx_task_id
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
# lib/cmdx_query_tagging_middleware.rb
|
|
22
|
-
class CmdxQueryTaggingMiddleware
|
|
23
|
-
def self.call(task, **options, &)
|
|
24
|
-
ActiveSupport::ExecutionContext.set(
|
|
25
|
-
cmdx_correlation_id: task.result.metadata[:correlation_id],
|
|
26
|
-
cmdx_chain_id: task.chain.id,
|
|
27
|
-
cmdx_task_class: task.class.name,
|
|
28
|
-
cmdx_task_id: task.id,
|
|
29
|
-
&
|
|
30
|
-
)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Usage
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
class MyTask < CMDx::Task
|
|
39
|
-
register :middleware, CmdxQueryTaggingMiddleware
|
|
40
|
-
|
|
41
|
-
def work
|
|
42
|
-
# Do work...
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
end
|
|
46
|
-
```
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Flipper Feature Flags
|
|
2
|
-
|
|
3
|
-
Control task execution based on Flipper feature flags.
|
|
4
|
-
|
|
5
|
-
<https://github.com/flippercloud/flipper>
|
|
6
|
-
|
|
7
|
-
### Setup
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
# lib/cmdx_flipper_middleware.rb
|
|
11
|
-
class CmdxFlipperMiddleware
|
|
12
|
-
def self.call(task, **options, &)
|
|
13
|
-
feature_name = options.fetch(:feature)
|
|
14
|
-
actor = options.fetch(:actor, -> { task.context[:user] })
|
|
15
|
-
|
|
16
|
-
# Resolve actor if it's a proc
|
|
17
|
-
actor = actor.call if actor.respond_to?(:call)
|
|
18
|
-
|
|
19
|
-
if Flipper.enabled?(feature_name, actor)
|
|
20
|
-
yield
|
|
21
|
-
else
|
|
22
|
-
# Option 1: Skip the task
|
|
23
|
-
task.skip!("Feature #{feature_name} is disabled")
|
|
24
|
-
|
|
25
|
-
# Option 2: Fail the task
|
|
26
|
-
# task.fail!("Feature #{feature_name} is disabled")
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Usage
|
|
33
|
-
|
|
34
|
-
```ruby
|
|
35
|
-
class NewFeatureTask < CMDx::Task
|
|
36
|
-
# Execute only if :new_feature is enabled for the user in context
|
|
37
|
-
register :middleware, CmdxFlipperMiddleware,
|
|
38
|
-
feature: :new_feature
|
|
39
|
-
|
|
40
|
-
# Customize the actor resolution
|
|
41
|
-
register :middleware, CmdxFlipperMiddleware,
|
|
42
|
-
feature: :beta_access,
|
|
43
|
-
actor: -> { task.context[:company] }
|
|
44
|
-
|
|
45
|
-
def work
|
|
46
|
-
# ...
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
```
|
|
50
|
-
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Paper Trail Whatdunnit
|
|
2
|
-
|
|
3
|
-
Tag paper trail version records with which service made a change with a custom `whatdunnit` attribute.
|
|
4
|
-
|
|
5
|
-
<https://github.com/paper-trail-gem/paper_trail?tab=readme-ov-file#4c-storing-metadata>
|
|
6
|
-
|
|
7
|
-
### Setup
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
# lib/cmdx_paper_trail_middleware.rb
|
|
11
|
-
class CmdxPaperTrailMiddleware
|
|
12
|
-
def self.call(task, **options, &)
|
|
13
|
-
# This makes sure to reset the whatdunnit value to the previous
|
|
14
|
-
# value for nested task calls
|
|
15
|
-
|
|
16
|
-
begin
|
|
17
|
-
PaperTrail.request.controller_info ||= {}
|
|
18
|
-
old_whatdunnit = PaperTrail.request.controller_info[:whatdunnit]
|
|
19
|
-
PaperTrail.request.controller_info[:whatdunnit] = task.class.name
|
|
20
|
-
yield
|
|
21
|
-
ensure
|
|
22
|
-
PaperTrail.request.controller_info[:whatdunnit] = old_whatdunnit
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Usage
|
|
29
|
-
|
|
30
|
-
```ruby
|
|
31
|
-
class MyTask < CMDx::Task
|
|
32
|
-
register :middleware, CmdxPaperTrailMiddleware
|
|
33
|
-
|
|
34
|
-
def work
|
|
35
|
-
# Do work...
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
end
|
|
39
|
-
```
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Redis Idempotency
|
|
2
|
-
|
|
3
|
-
Ensure tasks are executed exactly once using Redis to store execution state. This is critical for non-idempotent operations like charging a credit card or sending an email.
|
|
4
|
-
|
|
5
|
-
### Setup
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
8
|
-
# lib/cmdx_redis_idempotency_middleware.rb
|
|
9
|
-
class CmdxRedisIdempotencyMiddleware
|
|
10
|
-
def self.call(task, **options, &block)
|
|
11
|
-
key = generate_key(task, options[:key])
|
|
12
|
-
ttl = options[:ttl] || 5.minutes.to_i
|
|
13
|
-
|
|
14
|
-
# Attempt to lock the key
|
|
15
|
-
if Redis.current.set(key, "processing", nx: true, ex: ttl)
|
|
16
|
-
begin
|
|
17
|
-
block.call.tap |result|
|
|
18
|
-
Redis.current.set(key, result.status, xx: true, ex: ttl)
|
|
19
|
-
end
|
|
20
|
-
rescue => e
|
|
21
|
-
Redis.current.del(key)
|
|
22
|
-
raise(e)
|
|
23
|
-
end
|
|
24
|
-
else
|
|
25
|
-
# Key exists, handle duplicate
|
|
26
|
-
status = Redis.current.get(key)
|
|
27
|
-
|
|
28
|
-
if status == "processing"
|
|
29
|
-
task.result.tap { |r| r.skip!("Duplicate request: currently processing", halt: true) }
|
|
30
|
-
else
|
|
31
|
-
task.result.tap { |r| r.skip!("Duplicate request: already processed (#{status})", halt: true) }
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def self.generate_key(task, key_gen)
|
|
37
|
-
id = if key_gen.respond_to?(:call)
|
|
38
|
-
key_gen.call(task)
|
|
39
|
-
elsif key_gen.is_a?(Symbol)
|
|
40
|
-
task.send(key_gen)
|
|
41
|
-
else
|
|
42
|
-
task.context[:idempotency_key]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
"cmdx:idempotency:#{task.class.name}:#{id}"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Usage
|
|
51
|
-
|
|
52
|
-
```ruby
|
|
53
|
-
class ChargeCustomer < CMDx::Task
|
|
54
|
-
# Use context[:payment_id] as the unique key
|
|
55
|
-
register :middleware, CmdxIdempotencyMiddleware,
|
|
56
|
-
key: ->(t) { t.context[:payment_id] }
|
|
57
|
-
|
|
58
|
-
def work
|
|
59
|
-
# Charge logic...
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# First run: Executes
|
|
64
|
-
ChargeCustomer.call(payment_id: "123")
|
|
65
|
-
# => Success
|
|
66
|
-
|
|
67
|
-
# Second run: Skips
|
|
68
|
-
ChargeCustomer.call(payment_id: "123")
|
|
69
|
-
# => Skipped (reason: "Duplicate request: already processed (success)")
|
|
70
|
-
```
|
|
71
|
-
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Sentry Error Tracking
|
|
2
|
-
|
|
3
|
-
Report unhandled exceptions and unexpected task failures to Sentry with detailed context.
|
|
4
|
-
|
|
5
|
-
<https://github.com/getsentry/sentry-ruby>
|
|
6
|
-
|
|
7
|
-
### Setup
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
# lib/cmdx_sentry_middleware.rb
|
|
11
|
-
class CmdxSentryMiddleware
|
|
12
|
-
def self.call(task, **options, &)
|
|
13
|
-
Sentry.with_scope do |scope|
|
|
14
|
-
scope.set_tags(task: task.class.name)
|
|
15
|
-
scope.set_context(:user, Current.user.sentry_attributes)
|
|
16
|
-
|
|
17
|
-
yield.tap do |result|
|
|
18
|
-
# Optional: Report logical failures if needed
|
|
19
|
-
if Array(options[:report_on]).include?(result.status)
|
|
20
|
-
Sentry.capture_message("Task #{result.status}: #{result.reason}", level: :warning)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
rescue => e
|
|
25
|
-
Sentry.capture_exception(e)
|
|
26
|
-
raise(e) # Re-raise to let the task handle the error or bubble up
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Usage
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
class ProcessPayment < CMDx::Task
|
|
35
|
-
# Report exceptions only
|
|
36
|
-
register :middleware, CmdxSentryMiddleware
|
|
37
|
-
|
|
38
|
-
# Report exceptions AND logical failures (result.failure?)
|
|
39
|
-
register :middleware, CmdxSentryMiddleware, report_on: %w[failed skipped]
|
|
40
|
-
|
|
41
|
-
def work
|
|
42
|
-
# ...
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
```
|
|
46
|
-
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Sidekiq Async Execute
|
|
2
|
-
|
|
3
|
-
Execute tasks asynchronously using Sidekiq without creating separate job classes.
|
|
4
|
-
|
|
5
|
-
<https://github.com/sidekiq/sidekiq>
|
|
6
|
-
|
|
7
|
-
### Setup
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
class MyTask < CMDx::Task
|
|
11
|
-
include Sidekiq::Job
|
|
12
|
-
|
|
13
|
-
def work
|
|
14
|
-
# Do work...
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Use execute! to trigger Sidekiq's retry logic on failures/exceptions.
|
|
18
|
-
def perform
|
|
19
|
-
self.class.execute!
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
end
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Usage
|
|
26
|
-
|
|
27
|
-
```ruby
|
|
28
|
-
MyTask.perform_async
|
|
29
|
-
```
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Stoplight Circuit Breaker
|
|
2
|
-
|
|
3
|
-
Integrate circuit breakers to protect external service calls and prevent cascading failures when dependencies are unavailable.
|
|
4
|
-
|
|
5
|
-
<https://github.com/bolshakov/stoplight>
|
|
6
|
-
|
|
7
|
-
### Setup
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
# lib/cmdx_stoplight_middleware.rb
|
|
11
|
-
class CmdxStoplightMiddleware
|
|
12
|
-
def self.call(task, **options, &)
|
|
13
|
-
light = Stoplight(options[:name] || task.class.name, **options)
|
|
14
|
-
light.run(&)
|
|
15
|
-
rescue Stoplight::Error::RedLight => e
|
|
16
|
-
task.result.tap { |r| r.fail!("[#{e.class}] #{e.message}", cause: e) }
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Usage
|
|
22
|
-
|
|
23
|
-
```ruby
|
|
24
|
-
class MyTask < CMDx::Task
|
|
25
|
-
# With default options
|
|
26
|
-
register :middleware, CmdxStoplightMiddleware
|
|
27
|
-
|
|
28
|
-
# With stoplight options
|
|
29
|
-
register :middleware, CmdxStoplightMiddleware, cool_off_time: 10
|
|
30
|
-
|
|
31
|
-
def work
|
|
32
|
-
# Do work...
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
```
|
data/src/cmdx-dark-logo.png
DELETED
|
Binary file
|
data/src/cmdx-favicon.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg width="104.098" height="112.266" viewBox="0 0 60 64.708" xmlns="http://www.w3.org/2000/svg"><path d="M29.907 17.723 26.4 23.17 13.384 3.323h3.507l9.508 14.77 1.938-3.139L18.737 0H7.291L26.4 29.262 45.045 0h-3.97L30 17.54zM9.23 61.293H6.091l18.646-29.447L4.43.093H.46l20.308 31.846L0 64.708l10.985-.092 39.138-61.2h3.323L31.57 37.754l13.662 21.415h3.97L35.445 37.754 59.537.093H48.37zm29.63-23.262 15.047 23.262H43.384l-13.662-20.77-15.508 24.093h3.97l11.63-18 11.723 18H60L41.17 35.354l-.278-.461z" fill="#000"/></svg>
|
data/src/cmdx-light-logo.png
DELETED
|
Binary file
|
data/src/cmdx-logo.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg width="352.2" height="112.266" viewBox="0 0 203 64.708" xmlns="http://www.w3.org/2000/svg" xmlSpace="preserve"><path d="M172.908 17.723 169.4 23.17 156.385 3.323h3.507l9.508 14.77 1.938-3.139L161.738 0h-11.446L169.4 29.262 188.046 0h-3.97L173 17.54zm-20.677 43.57h-3.139l18.646-29.447L147.431.093h-3.97l20.308 31.846L143 64.708l10.985-.092 39.138-61.2h3.323L174.57 37.754l13.662 21.415h3.969l-13.754-21.415L202.538.093H191.37zm29.63-23.262 15.047 23.262h-10.523l-13.662-20.77-15.508 24.093h3.97l11.63-18 11.723 18H203l-18.83-29.262-.278-.461z" fill="#fe1817"/><path d="M41.667 14v12.8h-23.42c-3.214.272-5.665 3.05-5.665 6.318s2.45 5.937 5.664 6.318H33.17v4.248H18.246a10.65 10.65 0 0 1-9.858-10.62c0-5.447 4.194-10.077 9.64-10.512h19.39v-4.303H18.246v.054A14.823 14.823 0 0 0 4.248 33.118c0 7.898 6.21 14.38 13.998 14.815h19.172v-8.497h4.249v12.745h-23.42A19.063 19.063 0 0 1 0 33.118a19.033 19.033 0 0 1 18.246-19.063zM75 35.623 87.2 14h13.508v38.181H87.963v-14.27l-8.116 14.27h-9.749L57.734 30.504v-8.007l14.87 25.436h4.792l14.815-25.436v25.436h4.249V18.249H89.65l-14.76 25.49-14.542-25.49H53.54v29.684h4.194v-8.007l4.249 7.299v4.956H49.292v-38.18H62.8zM108.333 14h23.42C141.94 14.436 150 22.824 150 33.064c0 10.294-8.061 18.681-18.246 19.117h-23.42V22.497h23.42a10.65 10.65 0 0 1 9.858 10.621c0 5.447-4.194 10.13-9.64 10.566H116.83V30.286h4.248v9.15l10.676-.054c3.213-.273 5.664-3.05 5.664-6.264 0-2.778-1.743-5.065-4.194-5.991-.926-.382-1.47-.382-2.94-.382h-17.702v21.188h19.172a14.84 14.84 0 0 0 13.998-14.87c0-7.897-6.21-14.379-13.998-14.814h-23.42z"/></svg>
|