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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -76
  3. data/LICENSE.txt +3 -20
  4. data/README.md +8 -7
  5. data/lib/cmdx/attribute.rb +21 -5
  6. data/lib/cmdx/context.rb +16 -0
  7. data/lib/cmdx/executor.rb +9 -9
  8. data/lib/cmdx/result.rb +27 -7
  9. data/lib/cmdx/task.rb +19 -0
  10. data/lib/cmdx/version.rb +1 -1
  11. data/mkdocs.yml +62 -36
  12. metadata +3 -57
  13. data/.cursor/prompts/docs.md +0 -12
  14. data/.cursor/prompts/llms.md +0 -8
  15. data/.cursor/prompts/rspec.md +0 -24
  16. data/.cursor/prompts/yardoc.md +0 -15
  17. data/.cursor/rules/cursor-instructions.mdc +0 -68
  18. data/.irbrc +0 -18
  19. data/.rspec +0 -4
  20. data/.rubocop.yml +0 -95
  21. data/.ruby-version +0 -1
  22. data/.yard-lint.yml +0 -174
  23. data/.yardopts +0 -7
  24. data/docs/.DS_Store +0 -0
  25. data/docs/assets/favicon.ico +0 -0
  26. data/docs/assets/favicon.svg +0 -1
  27. data/docs/attributes/coercions.md +0 -155
  28. data/docs/attributes/defaults.md +0 -77
  29. data/docs/attributes/definitions.md +0 -283
  30. data/docs/attributes/naming.md +0 -68
  31. data/docs/attributes/transformations.md +0 -63
  32. data/docs/attributes/validations.md +0 -336
  33. data/docs/basics/chain.md +0 -108
  34. data/docs/basics/context.md +0 -121
  35. data/docs/basics/execution.md +0 -152
  36. data/docs/basics/setup.md +0 -107
  37. data/docs/callbacks.md +0 -157
  38. data/docs/configuration.md +0 -314
  39. data/docs/deprecation.md +0 -143
  40. data/docs/getting_started.md +0 -137
  41. data/docs/index.md +0 -134
  42. data/docs/internationalization.md +0 -126
  43. data/docs/interruptions/exceptions.md +0 -52
  44. data/docs/interruptions/faults.md +0 -169
  45. data/docs/interruptions/halt.md +0 -216
  46. data/docs/logging.md +0 -90
  47. data/docs/middlewares.md +0 -191
  48. data/docs/outcomes/result.md +0 -197
  49. data/docs/outcomes/states.md +0 -66
  50. data/docs/outcomes/statuses.md +0 -65
  51. data/docs/retries.md +0 -121
  52. data/docs/stylesheets/extra.css +0 -42
  53. data/docs/tips_and_tricks.md +0 -157
  54. data/docs/workflows.md +0 -226
  55. data/examples/active_record_database_transaction.md +0 -27
  56. data/examples/active_record_query_tagging.md +0 -46
  57. data/examples/flipper_feature_flags.md +0 -50
  58. data/examples/paper_trail_whatdunnit.md +0 -39
  59. data/examples/redis_idempotency.md +0 -71
  60. data/examples/sentry_error_tracking.md +0 -46
  61. data/examples/sidekiq_async_execution.md +0 -29
  62. data/examples/stoplight_circuit_breaker.md +0 -36
  63. data/src/cmdx-dark-logo.png +0 -0
  64. data/src/cmdx-favicon.svg +0 -1
  65. data/src/cmdx-light-logo.png +0 -0
  66. 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
- ```
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>
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>