axn 0.1.0.pre.alpha.2.8.1 → 0.1.0.pre.alpha.4

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/commands/pr.md +36 -0
  3. data/.cursor/rules/axn-framework-patterns.mdc +43 -0
  4. data/.cursor/rules/general-coding-standards.mdc +27 -0
  5. data/.cursor/rules/spec/testing-patterns.mdc +40 -0
  6. data/CHANGELOG.md +57 -0
  7. data/Rakefile +114 -4
  8. data/docs/.vitepress/config.mjs +19 -10
  9. data/docs/advanced/conventions.md +3 -3
  10. data/docs/advanced/mountable.md +476 -0
  11. data/docs/advanced/profiling.md +351 -0
  12. data/docs/advanced/rough.md +27 -8
  13. data/docs/index.md +5 -3
  14. data/docs/intro/about.md +1 -1
  15. data/docs/intro/overview.md +6 -6
  16. data/docs/recipes/formatting-context-for-error-tracking.md +186 -0
  17. data/docs/recipes/memoization.md +103 -18
  18. data/docs/recipes/rubocop-integration.md +38 -284
  19. data/docs/recipes/testing.md +14 -14
  20. data/docs/recipes/validating-user-input.md +1 -1
  21. data/docs/reference/async.md +429 -0
  22. data/docs/reference/axn-result.md +107 -0
  23. data/docs/reference/class.md +225 -64
  24. data/docs/reference/configuration.md +366 -34
  25. data/docs/reference/form-object.md +252 -0
  26. data/docs/reference/instance.md +14 -29
  27. data/docs/strategies/client.md +212 -0
  28. data/docs/strategies/form.md +235 -0
  29. data/docs/strategies/index.md +21 -21
  30. data/docs/strategies/transaction.md +1 -1
  31. data/docs/usage/setup.md +16 -2
  32. data/docs/usage/steps.md +7 -7
  33. data/docs/usage/using.md +23 -12
  34. data/docs/usage/writing.md +191 -12
  35. data/lib/axn/async/adapters/active_job.rb +74 -0
  36. data/lib/axn/async/adapters/disabled.rb +41 -0
  37. data/lib/axn/async/adapters/sidekiq.rb +67 -0
  38. data/lib/axn/async/adapters.rb +26 -0
  39. data/lib/axn/async/batch_enqueue/config.rb +38 -0
  40. data/lib/axn/async/batch_enqueue.rb +99 -0
  41. data/lib/axn/async/enqueue_all_orchestrator.rb +363 -0
  42. data/lib/axn/async.rb +178 -0
  43. data/lib/axn/configuration.rb +113 -0
  44. data/lib/{action → axn}/context.rb +22 -4
  45. data/lib/axn/core/automatic_logging.rb +89 -0
  46. data/lib/axn/core/context/facade.rb +69 -0
  47. data/lib/{action → axn}/core/context/facade_inspector.rb +32 -5
  48. data/lib/{action → axn}/core/context/internal.rb +5 -5
  49. data/lib/{action → axn}/core/contract.rb +111 -73
  50. data/lib/{action → axn}/core/contract_for_subfields.rb +30 -35
  51. data/lib/{action → axn}/core/contract_validation.rb +27 -12
  52. data/lib/axn/core/contract_validation_for_subfields.rb +165 -0
  53. data/lib/axn/core/default_call.rb +63 -0
  54. data/lib/axn/core/field_resolvers/extract.rb +32 -0
  55. data/lib/axn/core/field_resolvers/model.rb +63 -0
  56. data/lib/axn/core/field_resolvers.rb +24 -0
  57. data/lib/{action → axn}/core/flow/callbacks.rb +7 -7
  58. data/lib/{action → axn}/core/flow/exception_execution.rb +9 -13
  59. data/lib/{action → axn}/core/flow/handlers/base_descriptor.rb +3 -2
  60. data/lib/{action → axn}/core/flow/handlers/descriptors/callback_descriptor.rb +2 -2
  61. data/lib/{action → axn}/core/flow/handlers/descriptors/message_descriptor.rb +23 -11
  62. data/lib/axn/core/flow/handlers/invoker.rb +47 -0
  63. data/lib/{action → axn}/core/flow/handlers/matcher.rb +9 -19
  64. data/lib/{action → axn}/core/flow/handlers/registry.rb +3 -1
  65. data/lib/{action → axn}/core/flow/handlers/resolvers/base_resolver.rb +1 -1
  66. data/lib/{action → axn}/core/flow/handlers/resolvers/callback_resolver.rb +2 -2
  67. data/lib/{action → axn}/core/flow/handlers/resolvers/message_resolver.rb +12 -3
  68. data/lib/axn/core/flow/handlers.rb +20 -0
  69. data/lib/{action → axn}/core/flow/messages.rb +8 -8
  70. data/lib/{action → axn}/core/flow.rb +4 -4
  71. data/lib/{action → axn}/core/hooks.rb +17 -5
  72. data/lib/axn/core/logging.rb +48 -0
  73. data/lib/axn/core/memoization.rb +53 -0
  74. data/lib/{action → axn}/core/nesting_tracking.rb +1 -1
  75. data/lib/{action → axn}/core/timing.rb +1 -1
  76. data/lib/axn/core/tracing.rb +90 -0
  77. data/lib/axn/core/use_strategy.rb +29 -0
  78. data/lib/{action → axn}/core/validation/fields.rb +26 -2
  79. data/lib/{action → axn}/core/validation/subfields.rb +14 -12
  80. data/lib/axn/core/validation/validators/model_validator.rb +36 -0
  81. data/lib/axn/core/validation/validators/type_validator.rb +80 -0
  82. data/lib/{action → axn}/core/validation/validators/validate_validator.rb +12 -2
  83. data/lib/{action → axn}/core.rb +55 -55
  84. data/lib/{action → axn}/exceptions.rb +12 -2
  85. data/lib/axn/extras/strategies/client.rb +150 -0
  86. data/lib/axn/extras/strategies/vernier.rb +121 -0
  87. data/lib/axn/extras.rb +4 -0
  88. data/lib/axn/factory.rb +122 -34
  89. data/lib/axn/form_object.rb +90 -0
  90. data/lib/axn/internal/logging.rb +30 -0
  91. data/lib/axn/internal/registry.rb +87 -0
  92. data/lib/axn/mountable/descriptor.rb +76 -0
  93. data/lib/axn/mountable/helpers/class_builder.rb +193 -0
  94. data/lib/axn/mountable/helpers/mounter.rb +33 -0
  95. data/lib/axn/mountable/helpers/namespace_manager.rb +38 -0
  96. data/lib/axn/mountable/helpers/validator.rb +112 -0
  97. data/lib/axn/mountable/inherit_profiles.rb +72 -0
  98. data/lib/axn/mountable/mounting_strategies/_base.rb +87 -0
  99. data/lib/axn/mountable/mounting_strategies/axn.rb +48 -0
  100. data/lib/axn/mountable/mounting_strategies/method.rb +95 -0
  101. data/lib/axn/mountable/mounting_strategies/step.rb +69 -0
  102. data/lib/axn/mountable/mounting_strategies.rb +32 -0
  103. data/lib/axn/mountable.rb +119 -0
  104. data/lib/axn/rails/engine.rb +51 -0
  105. data/lib/axn/rails/generators/axn_generator.rb +86 -0
  106. data/lib/axn/rails/generators/templates/action.rb.erb +17 -0
  107. data/lib/axn/rails/generators/templates/action_spec.rb.erb +25 -0
  108. data/lib/{action → axn}/result.rb +32 -13
  109. data/lib/axn/strategies/form.rb +98 -0
  110. data/lib/axn/strategies/transaction.rb +26 -0
  111. data/lib/axn/strategies.rb +20 -0
  112. data/lib/axn/testing/spec_helpers.rb +6 -8
  113. data/lib/axn/util/callable.rb +120 -0
  114. data/lib/axn/util/contract_error_handling.rb +32 -0
  115. data/lib/axn/util/execution_context.rb +34 -0
  116. data/lib/axn/util/global_id_serialization.rb +52 -0
  117. data/lib/axn/util/logging.rb +87 -0
  118. data/lib/axn/util/memoization.rb +20 -0
  119. data/lib/axn/version.rb +1 -1
  120. data/lib/axn.rb +26 -16
  121. data/lib/rubocop/cop/axn/README.md +23 -23
  122. data/lib/rubocop/cop/axn/unchecked_result.rb +138 -17
  123. metadata +106 -64
  124. data/.rspec +0 -3
  125. data/.rubocop.yml +0 -76
  126. data/.tool-versions +0 -1
  127. data/docs/reference/action-result.md +0 -37
  128. data/lib/action/attachable/base.rb +0 -43
  129. data/lib/action/attachable/steps.rb +0 -63
  130. data/lib/action/attachable/subactions.rb +0 -70
  131. data/lib/action/attachable.rb +0 -17
  132. data/lib/action/configuration.rb +0 -55
  133. data/lib/action/core/automatic_logging.rb +0 -93
  134. data/lib/action/core/context/facade.rb +0 -48
  135. data/lib/action/core/flow/handlers/invoker.rb +0 -73
  136. data/lib/action/core/flow/handlers.rb +0 -20
  137. data/lib/action/core/logging.rb +0 -37
  138. data/lib/action/core/tracing.rb +0 -17
  139. data/lib/action/core/use_strategy.rb +0 -30
  140. data/lib/action/core/validation/validators/model_validator.rb +0 -34
  141. data/lib/action/core/validation/validators/type_validator.rb +0 -30
  142. data/lib/action/enqueueable/via_sidekiq.rb +0 -76
  143. data/lib/action/enqueueable.rb +0 -13
  144. data/lib/action/strategies/transaction.rb +0 -19
  145. data/lib/action/strategies.rb +0 -48
  146. data/lib/axn/util.rb +0 -24
  147. data/package.json +0 -10
  148. data/yarn.lock +0 -1166
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ module Util
5
+ module Callable
6
+ extend self
7
+
8
+ # Calls a callable with only the positional and keyword arguments it expects.
9
+ # If the callable accepts **kwargs (keyrest), passes all provided kwargs.
10
+ # If the callable accepts *args (rest), passes all provided positional args.
11
+ #
12
+ # @param callable [Proc, Method, #call] A callable object
13
+ # @param args [Array] An array of positional arguments to potentially pass
14
+ # @param kwargs [Hash] A hash of keyword arguments to potentially pass
15
+ # @return The return value of calling the callable
16
+ #
17
+ # @example
18
+ # proc = ->(resource:, result:) { }
19
+ # Callable.call_with_desired_shape(proc, kwargs: { resource: "Action", result: result, extra: "ignored" })
20
+ # # Calls proc with only resource: and result:
21
+ #
22
+ # @example
23
+ # proc = ->(a, b, c:) { }
24
+ # Callable.call_with_desired_shape(proc, args: [1, 2, 3, 4], kwargs: { c: 5, d: 6 })
25
+ # # Calls proc with args [1, 2, 3] and kwargs { c: 5 }
26
+ #
27
+ # @example
28
+ # proc = ->(**kwargs) { }
29
+ # Callable.call_with_desired_shape(proc, kwargs: { resource: "Action", result: result })
30
+ # # Calls proc with all kwargs
31
+ # Calls a callable with only the positional and keyword arguments it expects.
32
+ def call_with_desired_shape(callable, args: [], kwargs: {})
33
+ filtered_args, filtered_kwargs = only_requested_params(callable, args:, kwargs:)
34
+ callable.call(*filtered_args, **filtered_kwargs)
35
+ end
36
+
37
+ # Returns filtered args and kwargs for a callable without calling it.
38
+ # Useful when you need to execute the callable in a specific context (e.g., via instance_exec).
39
+ #
40
+ # @param callable [Proc, Method, #parameters] A callable object
41
+ # @param args [Array] An array of positional arguments to potentially pass
42
+ # @param kwargs [Hash] A hash of keyword arguments to potentially pass
43
+ # @return [Array<Array, Hash>] A tuple of [filtered_args, filtered_kwargs]
44
+ #
45
+ # @example
46
+ # proc = ->(resource:, result:) { }
47
+ # args, kwargs = Callable.only_requested_params(proc, kwargs: { resource: "Action", result: result, extra: "ignored" })
48
+ # # => [[], { resource: "Action", result: result }]
49
+ # action.instance_exec(*args, **kwargs, &proc)
50
+ def only_requested_params(callable, args: [], kwargs: {})
51
+ return [args, kwargs] unless callable.respond_to?(:parameters)
52
+
53
+ params = callable.parameters
54
+
55
+ # Determine which positional arguments to pass
56
+ filtered_args = filter_positional_args(params, args)
57
+
58
+ # Determine which keyword arguments to pass
59
+ filtered_kwargs = filter_kwargs(params, kwargs)
60
+
61
+ [filtered_args, filtered_kwargs]
62
+ end
63
+
64
+ # Returns filtered args and kwargs for a callable when passing an exception.
65
+ # The exception will be passed as either a positional argument or keyword argument,
66
+ # depending on what the callable expects.
67
+ #
68
+ # @param callable [Proc, Method, #parameters] A callable object
69
+ # @param exception [Exception, nil] The exception to potentially pass
70
+ # @return [Array<Array, Hash>] A tuple of [filtered_args, filtered_kwargs]
71
+ #
72
+ # @example
73
+ # proc = ->(exception:) { }
74
+ # args, kwargs = Callable.only_requested_params_for_exception(proc, exception)
75
+ # # => [[], { exception: exception }]
76
+ # action.instance_exec(*args, **kwargs, &proc)
77
+ #
78
+ # @example
79
+ # proc = ->(exception) { }
80
+ # args, kwargs = Callable.only_requested_params_for_exception(proc, exception)
81
+ # # => [[exception], {}]
82
+ # action.instance_exec(*args, **kwargs, &proc)
83
+ def only_requested_params_for_exception(callable, exception)
84
+ return [[], {}] unless exception
85
+
86
+ args = [exception]
87
+ kwargs = { exception: }
88
+ only_requested_params(callable, args:, kwargs:)
89
+ end
90
+
91
+ private
92
+
93
+ def filter_positional_args(params, args)
94
+ return args if args.empty?
95
+
96
+ required_count = params.count { |type, _name| type == :req }
97
+ optional_count = params.count { |type, _name| type == :opt }
98
+ has_rest = params.any? { |type, _name| type == :rest }
99
+
100
+ # If it accepts *args (rest), pass all provided args
101
+ return args if has_rest
102
+
103
+ # Otherwise, pass up to (required + optional) args
104
+ max_args = required_count + optional_count
105
+ args.first(max_args)
106
+ end
107
+
108
+ def filter_kwargs(params, kwargs)
109
+ return kwargs if kwargs.empty?
110
+
111
+ accepts_keyrest = params.any? { |type, _name| type == :keyrest }
112
+ return kwargs if accepts_keyrest
113
+
114
+ # Only pass explicitly expected keyword arguments
115
+ expected_keywords = params.select { |type, _name| %i[key keyreq].include?(type) }.map { |_type, name| name }
116
+ kwargs.select { |key, _value| expected_keywords.include?(key) }
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ module Util
5
+ module ContractErrorHandling
6
+ module_function
7
+
8
+ # Executes a block, allowing fail! and done! to propagate normally,
9
+ # but wrapping other StandardErrors in the specified exception class.
10
+ #
11
+ # @param exception_class [Class] The exception class to wrap errors in
12
+ # @param message [String, Proc] Error message or proc that takes (field_identifier, error)
13
+ # @param field_identifier [String] Identifier for the field (for error messages)
14
+ # @yield The block to execute
15
+ # @raise [Axn::Failure] Re-raised if raised in block
16
+ # @raise [Axn::Internal::EarlyCompletion] Re-raised if raised in block
17
+ # @raise [exception_class] Wrapped exception for other StandardErrors
18
+ def with_contract_error_handling(exception_class:, message:, field_identifier:)
19
+ yield
20
+ rescue Axn::Failure, Axn::Internal::EarlyCompletion => e
21
+ raise e # Re-raise control flow exceptions without wrapping
22
+ rescue StandardError => e
23
+ error_message = if message.is_a?(Proc)
24
+ message.call(field_identifier, e)
25
+ else
26
+ format(message, field_identifier, e.message)
27
+ end
28
+ raise exception_class, error_message, cause: e
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ module Util
5
+ module ExecutionContext
6
+ module_function
7
+
8
+ # Determines if code is currently running within a background job context.
9
+ # Checks all registered async adapters to see if any report running in background.
10
+ #
11
+ # @return [Boolean] true if running in a background job, false otherwise
12
+ #
13
+ # @example
14
+ # if Axn::Util::ExecutionContext.background?
15
+ # # Code is running in Sidekiq or ActiveJob
16
+ # end
17
+ def background?
18
+ Axn::Async::Adapters.all.values.any? do |adapter|
19
+ adapter.respond_to?(:_running_in_background?) && adapter._running_in_background?
20
+ end
21
+ rescue StandardError
22
+ false
23
+ end
24
+
25
+ # Determines if code is currently running in an interactive console (IRB, Pry, Rails console).
26
+ # Used to skip visual separators in console output since the prompt already provides separation.
27
+ #
28
+ # @return [Boolean] true if running in a console, false otherwise
29
+ def console?
30
+ defined?(Rails::Console) || defined?(IRB) || defined?(Pry)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ module Util
5
+ # Utilities for serializing/deserializing objects with GlobalID support.
6
+ # Used by async adapters to convert ActiveRecord objects to GlobalID strings
7
+ # for job serialization, and back to objects when the job runs.
8
+ module GlobalIdSerialization
9
+ GLOBAL_ID_SUFFIX = "_as_global_id"
10
+
11
+ class << self
12
+ # Serialize a hash for background job processing:
13
+ # - Convert GlobalID-able objects (e.g., ActiveRecord models) to GlobalID strings
14
+ # - Stringify keys for JSON compatibility
15
+ #
16
+ # @param params [Hash] The parameters to serialize
17
+ # @return [Hash] Serialized hash with string keys and GlobalID strings
18
+ def serialize(params)
19
+ return {} if params.nil? || params.empty?
20
+
21
+ params.each_with_object({}) do |(key, value), hash|
22
+ string_key = key.to_s
23
+ if value.respond_to?(:to_global_id)
24
+ hash["#{string_key}#{GLOBAL_ID_SUFFIX}"] = value.to_global_id.to_s
25
+ else
26
+ hash[string_key] = value
27
+ end
28
+ end
29
+ end
30
+
31
+ # Deserialize a hash from background job processing:
32
+ # - Convert GlobalID strings back to objects
33
+ # - Symbolize keys for use with kwargs
34
+ #
35
+ # @param params [Hash] The serialized parameters
36
+ # @return [Hash] Deserialized hash with symbol keys and resolved objects
37
+ def deserialize(params)
38
+ return {} if params.nil? || params.empty?
39
+
40
+ params.each_with_object({}) do |(key, value), hash|
41
+ if key.end_with?(GLOBAL_ID_SUFFIX)
42
+ original_key = key.delete_suffix(GLOBAL_ID_SUFFIX).to_sym
43
+ hash[original_key] = GlobalID::Locator.locate(value)
44
+ else
45
+ hash[key.to_sym] = value
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ module Util
5
+ module Logging
6
+ extend self
7
+
8
+ MAX_CONTEXT_LENGTH = 150
9
+ TRUNCATION_SUFFIX = "…<truncated>…"
10
+
11
+ # Logs a message at the specified level with error handling
12
+ # @param action_class [Class] The action class to log from
13
+ # @param level [Symbol] The log level (e.g., :info, :warn)
14
+ # @param message_parts [Array<String>] Parts of the message to join
15
+ # @param error_context [String] Context for error reporting if logging fails
16
+ # @param join_string [String] String to join message parts with
17
+ # @param before [String, nil] Text to prepend to the message
18
+ # @param after [String, nil] Text to append to the message
19
+ # @param prefix [String, nil] Override the default log prefix (useful for class-level logging)
20
+ # @param context_direction [Symbol, nil] Direction for context logging (:inbound or :outbound)
21
+ # @param context_instance [Object, nil] Action instance for instance-level context_for_logging
22
+ # @param context_data [Hash, nil] Raw data for class-level context_for_logging
23
+ def log_at_level(
24
+ action_class,
25
+ level:,
26
+ message_parts:,
27
+ error_context:,
28
+ join_string: " ",
29
+ before: nil,
30
+ after: nil,
31
+ prefix: nil,
32
+ context_direction: nil,
33
+ context_instance: nil,
34
+ context_data: nil
35
+ )
36
+ return unless level
37
+
38
+ # Prepare and format context if needed
39
+ context_str = if context_instance && context_direction
40
+ # Instance-level context_for_logging
41
+ data = context_instance.context_for_logging(context_direction)
42
+ format_context(data)
43
+ elsif context_data && context_direction
44
+ # Class-level context_for_logging
45
+ data = action_class.context_for_logging(data: context_data, direction: context_direction)
46
+ format_context(data)
47
+ end
48
+
49
+ # Add context to message parts if present
50
+ full_message_parts = context_str ? message_parts + [context_str] : message_parts
51
+ message = full_message_parts.compact.join(join_string)
52
+
53
+ action_class.public_send(level, message, before:, after:, prefix:)
54
+ rescue StandardError => e
55
+ Axn::Internal::Logging.piping_error(error_context, action: action_class, exception: e)
56
+ end
57
+
58
+ private
59
+
60
+ # Formats context data for logging, with truncation if needed
61
+ def format_context(data)
62
+ return unless data.present?
63
+
64
+ formatted = format_object(data)
65
+ return formatted if formatted.length <= MAX_CONTEXT_LENGTH
66
+
67
+ formatted[0, MAX_CONTEXT_LENGTH - TRUNCATION_SUFFIX.length] + TRUNCATION_SUFFIX
68
+ end
69
+
70
+ # Formats an object for logging, handling special cases for ActiveRecord and ActionController::Parameters
71
+ def format_object(data)
72
+ case data
73
+ when Hash
74
+ # NOTE: slightly more manual in order to avoid quotes around ActiveRecord objects' <Class#id> formatting
75
+ "{#{data.map { |k, v| "#{k}: #{format_object(v)}" }.join(', ')}}"
76
+ when Array
77
+ data.map { |v| format_object(v) }
78
+ else
79
+ return data.to_unsafe_h if defined?(ActionController::Parameters) && data.is_a?(ActionController::Parameters)
80
+ return "<#{data.class.name}##{data.to_param.presence || 'unpersisted'}>" if defined?(ActiveRecord::Base) && data.is_a?(ActiveRecord::Base)
81
+
82
+ data.inspect
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ module Util
5
+ module Memoization
6
+ UNSET = Object.new.freeze
7
+
8
+ def self.define_memoized_reader_method(target, field, &block)
9
+ target.define_method(field) do
10
+ ivar = :"@_memoized_reader_#{field}"
11
+ cached_val = instance_variable_defined?(ivar) ? instance_variable_get(ivar) : UNSET
12
+ return cached_val unless cached_val == UNSET
13
+
14
+ value = instance_exec(&block)
15
+ instance_variable_set(ivar, value)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/axn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Axn
4
- VERSION = "0.1.0-alpha.2.8.1"
4
+ VERSION = "0.1.0-alpha.4"
5
5
  end
data/lib/axn.rb CHANGED
@@ -1,37 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support"
4
+ require "active_support/concern"
4
5
 
5
- module Axn; end
6
+ # Standalone
6
7
  require "axn/version"
7
- require "axn/util"
8
8
  require "axn/factory"
9
+ require "axn/configuration"
10
+ require "axn/exceptions"
9
11
 
10
- require "action/configuration"
11
- require "action/exceptions"
12
+ # The core implementation
13
+ require "axn/core"
12
14
 
13
- require "action/core"
15
+ # Utilities
16
+ require "axn/util/memoization"
17
+ require "axn/util/callable"
18
+ require "axn/util/logging"
19
+ require "axn/util/execution_context"
20
+ require "axn/util/contract_error_handling"
21
+ require "axn/util/global_id_serialization"
22
+ require "axn/form_object"
14
23
 
15
- require "action/attachable"
16
- require "action/enqueueable"
24
+ # Extensions
25
+ require "axn/mountable"
26
+ require "axn/async"
17
27
 
18
- def Axn(callable, **) # rubocop:disable Naming/MethodName
19
- return callable if callable.is_a?(Class) && callable < Action
28
+ # Rails integration (if in Rails context)
29
+ require "axn/rails/engine" if defined?(Rails) && Rails.const_defined?(:Engine)
20
30
 
21
- Axn::Factory.build(**, &callable)
22
- end
23
-
24
- module Action
31
+ module Axn
25
32
  def self.included(base)
26
33
  base.class_eval do
27
34
  include Core
28
35
 
29
36
  # --- Extensions ---
30
- include Attachable
31
- include Enqueueable
37
+ include Mountable
38
+ include Async
32
39
 
33
40
  # Allow additional automatic includes to be configured
34
- Array(Action.config.additional_includes).each { |mod| include mod }
41
+ Array(Axn.config.additional_includes).each { |mod| include mod }
35
42
  end
36
43
  end
37
44
  end
45
+
46
+ # Load after Axn is defined since it includes Axn
47
+ require "axn/async/enqueue_all_orchestrator"
@@ -4,11 +4,11 @@ This directory contains custom RuboCop cops specifically designed for the Axn li
4
4
 
5
5
  ## Axn/UncheckedResult
6
6
 
7
- This cop enforces proper result handling when calling Actions. It can be configured to check nested calls, non-nested calls, or both.
7
+ This cop enforces proper result handling when calling Axns. It can be configured to check nested calls, non-nested calls, or both.
8
8
 
9
9
  ### Why This Rule Exists
10
10
 
11
- When Actions are nested, proper error handling becomes crucial. Without proper result checking, failures in nested Actions can be silently ignored, leading to:
11
+ When Axns are nested, proper error handling becomes crucial. Without proper result checking, failures in nested Axns can be silently ignored, leading to:
12
12
 
13
13
  - Silent failures that are hard to debug
14
14
  - Inconsistent error handling patterns
@@ -21,8 +21,8 @@ The cop supports flexible configuration to match your team's needs:
21
21
  ```yaml
22
22
  Axn/UncheckedResult:
23
23
  Enabled: true
24
- CheckNested: true # Check nested Action calls (default: true)
25
- CheckNonNested: true # Check non-nested Action calls (default: true)
24
+ CheckNested: true # Check nested Axn calls (default: true)
25
+ CheckNonNested: true # Check non-nested Axn calls (default: true)
26
26
  Severity: warning # or error, if you want to enforce it strictly
27
27
  ```
28
28
 
@@ -33,21 +33,21 @@ Axn/UncheckedResult:
33
33
  CheckNested: true
34
34
  CheckNonNested: true
35
35
  ```
36
- Checks all Action calls regardless of nesting.
36
+ Checks all Axn calls regardless of nesting.
37
37
 
38
38
  2. **Nested Only**:
39
39
  ```yaml
40
40
  CheckNested: true
41
41
  CheckNonNested: false
42
42
  ```
43
- Only checks Action calls from within other Actions.
43
+ Only checks Axn calls from within other Axns.
44
44
 
45
45
  3. **Non-Nested Only**:
46
46
  ```yaml
47
47
  CheckNested: false
48
48
  CheckNonNested: true
49
49
  ```
50
- Only checks top-level Action calls.
50
+ Only checks top-level Axn calls.
51
51
 
52
52
  4. **Disabled**:
53
53
  ```yaml
@@ -62,7 +62,7 @@ Axn/UncheckedResult:
62
62
 
63
63
  ```ruby
64
64
  class OuterAction
65
- include Action
65
+ include Axn
66
66
  def call
67
67
  InnerAction.call(param: "value") # Missing result check
68
68
  # This will always continue even if InnerAction fails
@@ -74,7 +74,7 @@ end
74
74
 
75
75
  ```ruby
76
76
  class OuterAction
77
- include Action
77
+ include Axn
78
78
  def call
79
79
  InnerAction.call!(param: "value") # Using call! ensures exceptions bubble up
80
80
  end
@@ -85,7 +85,7 @@ end
85
85
 
86
86
  ```ruby
87
87
  class OuterAction
88
- include Action
88
+ include Axn
89
89
  def call
90
90
  result = InnerAction.call(param: "value")
91
91
  return result unless result.ok?
@@ -98,7 +98,7 @@ end
98
98
 
99
99
  ```ruby
100
100
  class OuterAction
101
- include Action
101
+ include Axn
102
102
  def call
103
103
  result = InnerAction.call(param: "value")
104
104
  if result.failed?
@@ -113,7 +113,7 @@ end
113
113
 
114
114
  ```ruby
115
115
  class OuterAction
116
- include Action
116
+ include Axn
117
117
  def call
118
118
  result = InnerAction.call(param: "value")
119
119
  if result.error
@@ -128,7 +128,7 @@ end
128
128
 
129
129
  ```ruby
130
130
  class OuterAction
131
- include Action
131
+ include Axn
132
132
  def call
133
133
  result = InnerAction.call(param: "value")
134
134
  result # Result is returned, so it's properly handled
@@ -140,7 +140,7 @@ end
140
140
 
141
141
  ```ruby
142
142
  class OuterAction
143
- include Action
143
+ include Axn
144
144
  exposes :nested_result
145
145
  def call
146
146
  result = InnerAction.call(param: "value")
@@ -153,7 +153,7 @@ end
153
153
 
154
154
  ```ruby
155
155
  class OuterAction
156
- include Action
156
+ include Axn
157
157
  def call
158
158
  result = InnerAction.call(param: "value")
159
159
  process_result(result) # Result is used, so it's properly handled
@@ -165,19 +165,19 @@ end
165
165
 
166
166
  The cop analyzes your code to determine if you're:
167
167
 
168
- 1. **Inside an Action class** - Classes that `include Action`
168
+ 1. **Inside an Axn class** - Classes that `include Axn`
169
169
  2. **Inside the `call` method** - Only the main execution method
170
- 3. **Calling another Action** - Using `.call` on Action classes
170
+ 3. **Calling another Axn** - Using `.call` on Axn classes
171
171
  4. **Properly handling the result** - One of the acceptable patterns above
172
172
 
173
173
  ### What the Cop Ignores
174
174
 
175
175
  The cop will NOT report offenses for:
176
176
 
177
- - Action calls outside of Action classes
178
- - Action calls in methods other than `call`
179
- - Action calls that use `call!` (bang method)
180
- - Action calls where the result is properly handled
177
+ - Axn calls outside of Axn classes
178
+ - Axn calls in methods other than `call`
179
+ - Axn calls that use `call!` (bang method)
180
+ - Axn calls where the result is properly handled
181
181
 
182
182
  ### Configuration
183
183
 
@@ -189,8 +189,8 @@ require:
189
189
 
190
190
  Axn/UncheckedResult:
191
191
  Enabled: true
192
- CheckNested: true # Check nested Action calls
193
- CheckNonNested: true # Check non-nested Action calls
192
+ CheckNested: true # Check nested Axn calls
193
+ CheckNonNested: true # Check non-nested Axn calls
194
194
  Severity: warning # or error, if you want to enforce it strictly
195
195
  ```
196
196