light-services 3.0.0 → 3.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -1
  3. data/CHANGELOG.md +21 -0
  4. data/CLAUDE.md +1 -1
  5. data/Gemfile.lock +1 -1
  6. data/README.md +11 -11
  7. data/docs/{readme.md → README.md} +12 -11
  8. data/docs/{summary.md → SUMMARY.md} +11 -1
  9. data/docs/arguments.md +23 -0
  10. data/docs/concepts.md +19 -19
  11. data/docs/configuration.md +36 -0
  12. data/docs/errors.md +31 -1
  13. data/docs/outputs.md +23 -0
  14. data/docs/quickstart.md +1 -1
  15. data/docs/rubocop.md +285 -0
  16. data/docs/ruby-lsp.md +133 -0
  17. data/docs/steps.md +62 -8
  18. data/docs/testing.md +1 -1
  19. data/lib/light/services/base.rb +110 -7
  20. data/lib/light/services/base_with_context.rb +23 -1
  21. data/lib/light/services/callbacks.rb +293 -41
  22. data/lib/light/services/collection.rb +50 -2
  23. data/lib/light/services/concerns/execution.rb +3 -0
  24. data/lib/light/services/config.rb +83 -3
  25. data/lib/light/services/constants.rb +3 -0
  26. data/lib/light/services/dsl/arguments_dsl.rb +1 -0
  27. data/lib/light/services/dsl/outputs_dsl.rb +1 -0
  28. data/lib/light/services/dsl/validation.rb +30 -0
  29. data/lib/light/services/exceptions.rb +19 -1
  30. data/lib/light/services/message.rb +28 -3
  31. data/lib/light/services/messages.rb +74 -2
  32. data/lib/light/services/rubocop/cop/light_services/argument_type_required.rb +52 -0
  33. data/lib/light/services/rubocop/cop/light_services/condition_method_exists.rb +173 -0
  34. data/lib/light/services/rubocop/cop/light_services/deprecated_methods.rb +113 -0
  35. data/lib/light/services/rubocop/cop/light_services/dsl_order.rb +176 -0
  36. data/lib/light/services/rubocop/cop/light_services/missing_private_keyword.rb +102 -0
  37. data/lib/light/services/rubocop/cop/light_services/no_direct_instantiation.rb +66 -0
  38. data/lib/light/services/rubocop/cop/light_services/output_type_required.rb +52 -0
  39. data/lib/light/services/rubocop/cop/light_services/step_method_exists.rb +109 -0
  40. data/lib/light/services/rubocop.rb +12 -0
  41. data/lib/light/services/settings/field.rb +33 -5
  42. data/lib/light/services/settings/step.rb +23 -5
  43. data/lib/light/services/version.rb +1 -1
  44. data/lib/ruby_lsp/light_services/addon.rb +36 -0
  45. data/lib/ruby_lsp/light_services/definition.rb +132 -0
  46. data/lib/ruby_lsp/light_services/indexing_enhancement.rb +263 -0
  47. metadata +17 -3
@@ -1,9 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This class allows running a service object with context (parent class and custom config)
4
3
  module Light
5
4
  module Services
5
+ # Wrapper for running a service with a parent context or custom configuration.
6
+ # Created via {Base.with} method.
7
+ #
8
+ # @example Running with parent service context
9
+ # ChildService.with(self).run(data: value)
10
+ #
11
+ # @example Running with custom configuration
12
+ # MyService.with(use_transactions: false).run(name: "test")
6
13
  class BaseWithContext
14
+ # Initialize a new context wrapper.
15
+ #
16
+ # @param service_class [Class] the service class to run
17
+ # @param parent_service [Base, nil] parent service for error/warning propagation
18
+ # @param config [Hash] configuration overrides
19
+ # @raise [ArgTypeError] if parent_service is not a Base subclass
7
20
  def initialize(service_class, parent_service, config)
8
21
  @service_class = service_class
9
22
  @config = config
@@ -14,10 +27,19 @@ module Light
14
27
  raise Light::Services::ArgTypeError, "#{parent_service.class} - must be a subclass of Light::Services::Base"
15
28
  end
16
29
 
30
+ # Run the service with the configured context.
31
+ #
32
+ # @param args [Hash] arguments to pass to the service
33
+ # @return [Base] the executed service instance
17
34
  def run(args = {})
18
35
  @service_class.new(extend_arguments(args), @config, @parent_service).tap(&:call)
19
36
  end
20
37
 
38
+ # Run the service and raise an error if it fails.
39
+ #
40
+ # @param args [Hash] arguments to pass to the service
41
+ # @return [Base] the executed service instance
42
+ # @raise [Error] if the service fails
21
43
  def run!(args = {})
22
44
  @config[:raise_on_error] = true
23
45
  run(args)
@@ -2,7 +2,38 @@
2
2
 
3
3
  module Light
4
4
  module Services
5
+ # Provides callback hooks for service and step lifecycle events.
6
+ #
7
+ # @example Service-level callbacks
8
+ # class MyService < Light::Services::Base
9
+ # before_service_run :log_start
10
+ # after_service_run { |service| Rails.logger.info("Done!") }
11
+ # on_service_success :send_notification
12
+ # on_service_failure :log_error
13
+ # end
14
+ #
15
+ # @example Step-level callbacks
16
+ # class MyService < Light::Services::Base
17
+ # before_step_run :log_step_start
18
+ # after_step_run { |service, step_name| puts "Finished #{step_name}" }
19
+ # on_step_failure :handle_step_error
20
+ # end
21
+ #
22
+ # @example Around callbacks
23
+ # class MyService < Light::Services::Base
24
+ # around_service_run :with_timing
25
+ #
26
+ # private
27
+ #
28
+ # def with_timing(service)
29
+ # start = Time.now
30
+ # yield
31
+ # puts "Took #{Time.now - start}s"
32
+ # end
33
+ # end
5
34
  module Callbacks
35
+ # Available callback events.
36
+ # @return [Array<Symbol>] list of callback event names
6
37
  EVENTS = [
7
38
  :before_step_run,
8
39
  :after_step_run,
@@ -17,47 +48,12 @@ module Light
17
48
  :on_service_failure,
18
49
  ].freeze
19
50
 
20
- def self.included(base)
21
- base.extend(ClassMethods)
22
- end
23
-
24
- module ClassMethods
25
- # Define DSL methods for each callback event
26
- EVENTS.each do |event|
27
- define_method(event) do |method_name = nil, &block|
28
- callback = method_name || block
29
- raise ArgumentError, "#{event} requires a method name (symbol) or a block" unless callback
30
-
31
- unless callback.is_a?(Symbol) || callback.is_a?(Proc)
32
- raise ArgumentError,
33
- "#{event} callback must be a Symbol or Proc"
34
- end
35
-
36
- callbacks_for(event) << callback
37
- end
38
- end
39
-
40
- # Get all callbacks for a specific event (including inherited ones)
41
- def callbacks_for(event)
42
- @callbacks ||= {}
43
- @callbacks[event] ||= []
44
- end
45
-
46
- # Get all callbacks including inherited ones
47
- def all_callbacks_for(event)
48
- if superclass.respond_to?(:all_callbacks_for)
49
- inherited = superclass.all_callbacks_for(event)
50
- else
51
- inherited = []
52
- end
53
-
54
- inherited + callbacks_for(event)
55
- end
56
- end
57
-
58
- # Run callbacks for a given event
59
- # For around callbacks, yields to the block
60
- # For other callbacks, just executes them in order
51
+ # Run all callbacks for a given event.
52
+ #
53
+ # @param event [Symbol] the callback event name
54
+ # @param args [Array] arguments to pass to callbacks
55
+ # @yield for around callbacks, the block to wrap
56
+ # @return [void]
61
57
  def run_callbacks(event, *args, &block)
62
58
  callbacks = self.class.all_callbacks_for(event)
63
59
 
@@ -99,5 +95,261 @@ module Light
99
95
  end
100
96
  end
101
97
  end
98
+
99
+ # Class methods for registering callbacks.
100
+ # Extend this module in your service class to get callback DSL methods.
101
+ #
102
+ # @example
103
+ # class MyService < Light::Services::Base
104
+ # before_service_run :setup
105
+ # after_service_run :cleanup
106
+ # end
107
+ module CallbackDsl
108
+ # Registers a callback to run before each step executes.
109
+ #
110
+ # @param method_name [Symbol, nil] name of the instance method to call
111
+ # @yield [service, step_name] block to execute if no method name provided
112
+ # @yieldparam service [Light::Services::Base] the service instance
113
+ # @yieldparam step_name [Symbol] the name of the step about to run
114
+ # @return [void]
115
+ # @raise [ArgumentError] if neither method name nor block is provided
116
+ #
117
+ # @example With method name
118
+ # before_step_run :log_step_start
119
+ #
120
+ # @example With block
121
+ # before_step_run { |service, step_name| puts "Starting #{step_name}" }
122
+ def before_step_run(method_name = nil, &block)
123
+ register_callback(:before_step_run, method_name, &block)
124
+ end
125
+
126
+ # Registers a callback to run after each step executes.
127
+ #
128
+ # @param method_name [Symbol, nil] name of the instance method to call
129
+ # @yield [service, step_name] block to execute if no method name provided
130
+ # @yieldparam service [Light::Services::Base] the service instance
131
+ # @yieldparam step_name [Symbol] the name of the step that just ran
132
+ # @return [void]
133
+ # @raise [ArgumentError] if neither method name nor block is provided
134
+ #
135
+ # @example With method name
136
+ # after_step_run :log_step_complete
137
+ #
138
+ # @example With block
139
+ # after_step_run { |service, step_name| puts "Finished #{step_name}" }
140
+ def after_step_run(method_name = nil, &block)
141
+ register_callback(:after_step_run, method_name, &block)
142
+ end
143
+
144
+ # Registers an around callback that wraps each step execution.
145
+ # The callback must yield to execute the step.
146
+ #
147
+ # @param method_name [Symbol, nil] name of the instance method to call
148
+ # @yield [service, step_name] block to execute if no method name provided
149
+ # @yieldparam service [Light::Services::Base] the service instance
150
+ # @yieldparam step_name [Symbol] the name of the step being wrapped
151
+ # @return [void]
152
+ # @raise [ArgumentError] if neither method name nor block is provided
153
+ #
154
+ # @example With method name
155
+ # around_step_run :with_step_timing
156
+ #
157
+ # def with_step_timing(service, step_name)
158
+ # start = Time.now
159
+ # yield
160
+ # puts "#{step_name} took #{Time.now - start}s"
161
+ # end
162
+ def around_step_run(method_name = nil, &block)
163
+ register_callback(:around_step_run, method_name, &block)
164
+ end
165
+
166
+ # Registers a callback to run when a step completes successfully (without adding errors).
167
+ #
168
+ # @param method_name [Symbol, nil] name of the instance method to call
169
+ # @yield [service, step_name] block to execute if no method name provided
170
+ # @yieldparam service [Light::Services::Base] the service instance
171
+ # @yieldparam step_name [Symbol] the name of the successful step
172
+ # @return [void]
173
+ # @raise [ArgumentError] if neither method name nor block is provided
174
+ #
175
+ # @example With method name
176
+ # on_step_success :track_step_success
177
+ #
178
+ # @example With block
179
+ # on_step_success { |service, step_name| Analytics.track("step.success", step: step_name) }
180
+ def on_step_success(method_name = nil, &block)
181
+ register_callback(:on_step_success, method_name, &block)
182
+ end
183
+
184
+ # Registers a callback to run when a step fails (adds errors).
185
+ #
186
+ # @param method_name [Symbol, nil] name of the instance method to call
187
+ # @yield [service, step_name] block to execute if no method name provided
188
+ # @yieldparam service [Light::Services::Base] the service instance
189
+ # @yieldparam step_name [Symbol] the name of the failed step
190
+ # @return [void]
191
+ # @raise [ArgumentError] if neither method name nor block is provided
192
+ #
193
+ # @example With method name
194
+ # on_step_failure :handle_step_error
195
+ #
196
+ # @example With block
197
+ # on_step_failure { |service, step_name| Rails.logger.error("Step #{step_name} failed") }
198
+ def on_step_failure(method_name = nil, &block)
199
+ register_callback(:on_step_failure, method_name, &block)
200
+ end
201
+
202
+ # Registers a callback to run when a step raises an exception.
203
+ #
204
+ # @param method_name [Symbol, nil] name of the instance method to call
205
+ # @yield [service, step_name, exception] block to execute if no method name provided
206
+ # @yieldparam service [Light::Services::Base] the service instance
207
+ # @yieldparam step_name [Symbol] the name of the crashed step
208
+ # @yieldparam exception [Exception] the exception that was raised
209
+ # @return [void]
210
+ # @raise [ArgumentError] if neither method name nor block is provided
211
+ #
212
+ # @example With method name
213
+ # on_step_crash :report_crash
214
+ #
215
+ # @example With block
216
+ # on_step_crash { |service, step_name, error| Sentry.capture_exception(error) }
217
+ def on_step_crash(method_name = nil, &block)
218
+ register_callback(:on_step_crash, method_name, &block)
219
+ end
220
+
221
+ # Registers a callback to run before the service starts executing.
222
+ #
223
+ # @param method_name [Symbol, nil] name of the instance method to call
224
+ # @yield [service] block to execute if no method name provided
225
+ # @yieldparam service [Light::Services::Base] the service instance
226
+ # @return [void]
227
+ # @raise [ArgumentError] if neither method name nor block is provided
228
+ #
229
+ # @example With method name
230
+ # before_service_run :log_start
231
+ #
232
+ # @example With block
233
+ # before_service_run { |service| Rails.logger.info("Starting #{service.class.name}") }
234
+ def before_service_run(method_name = nil, &block)
235
+ register_callback(:before_service_run, method_name, &block)
236
+ end
237
+
238
+ # Registers a callback to run after the service completes (regardless of success/failure).
239
+ #
240
+ # @param method_name [Symbol, nil] name of the instance method to call
241
+ # @yield [service] block to execute if no method name provided
242
+ # @yieldparam service [Light::Services::Base] the service instance
243
+ # @return [void]
244
+ # @raise [ArgumentError] if neither method name nor block is provided
245
+ #
246
+ # @example With method name
247
+ # after_service_run :cleanup
248
+ #
249
+ # @example With block
250
+ # after_service_run { |service| Rails.logger.info("Done!") }
251
+ def after_service_run(method_name = nil, &block)
252
+ register_callback(:after_service_run, method_name, &block)
253
+ end
254
+
255
+ # Registers an around callback that wraps the entire service execution.
256
+ # The callback must yield to execute the service.
257
+ #
258
+ # @param method_name [Symbol, nil] name of the instance method to call
259
+ # @yield [service] block to execute if no method name provided
260
+ # @yieldparam service [Light::Services::Base] the service instance
261
+ # @return [void]
262
+ # @raise [ArgumentError] if neither method name nor block is provided
263
+ #
264
+ # @example With method name
265
+ # around_service_run :with_timing
266
+ #
267
+ # def with_timing(service)
268
+ # start = Time.now
269
+ # yield
270
+ # puts "Took #{Time.now - start}s"
271
+ # end
272
+ def around_service_run(method_name = nil, &block)
273
+ register_callback(:around_service_run, method_name, &block)
274
+ end
275
+
276
+ # Registers a callback to run when the service completes successfully (without errors).
277
+ #
278
+ # @param method_name [Symbol, nil] name of the instance method to call
279
+ # @yield [service] block to execute if no method name provided
280
+ # @yieldparam service [Light::Services::Base] the service instance
281
+ # @return [void]
282
+ # @raise [ArgumentError] if neither method name nor block is provided
283
+ #
284
+ # @example With method name
285
+ # on_service_success :send_notification
286
+ #
287
+ # @example With block
288
+ # on_service_success { |service| NotificationMailer.success(service.user).deliver_later }
289
+ def on_service_success(method_name = nil, &block)
290
+ register_callback(:on_service_success, method_name, &block)
291
+ end
292
+
293
+ # Registers a callback to run when the service completes with errors.
294
+ #
295
+ # @param method_name [Symbol, nil] name of the instance method to call
296
+ # @yield [service] block to execute if no method name provided
297
+ # @yieldparam service [Light::Services::Base] the service instance
298
+ # @return [void]
299
+ # @raise [ArgumentError] if neither method name nor block is provided
300
+ #
301
+ # @example With method name
302
+ # on_service_failure :log_error
303
+ #
304
+ # @example With block
305
+ # on_service_failure { |service| Rails.logger.error(service.errors.full_messages) }
306
+ def on_service_failure(method_name = nil, &block)
307
+ register_callback(:on_service_failure, method_name, &block)
308
+ end
309
+
310
+ # Get callbacks defined in this class for a specific event.
311
+ #
312
+ # @param event [Symbol] the callback event name
313
+ # @return [Array<Symbol, Proc>] callbacks for this event
314
+ def callbacks_for(event)
315
+ @callbacks ||= {}
316
+ @callbacks[event] ||= []
317
+ end
318
+
319
+ # Get all callbacks for an event including inherited ones.
320
+ #
321
+ # @param event [Symbol] the callback event name
322
+ # @return [Array<Symbol, Proc>] all callbacks for this event
323
+ def all_callbacks_for(event)
324
+ if superclass.respond_to?(:all_callbacks_for)
325
+ inherited = superclass.all_callbacks_for(event)
326
+ else
327
+ inherited = []
328
+ end
329
+
330
+ inherited + callbacks_for(event)
331
+ end
332
+
333
+ private
334
+
335
+ # Registers a callback for a given event.
336
+ #
337
+ # @param event [Symbol] the callback event name
338
+ # @param method_name [Symbol, nil] name of the instance method to call
339
+ # @yield block to execute if no method name provided
340
+ # @return [void]
341
+ # @raise [ArgumentError] if neither method name nor block is provided
342
+ # @api private
343
+ def register_callback(event, method_name = nil, &block)
344
+ callback = method_name || block
345
+ raise ArgumentError, "#{event} requires a method name (symbol) or a block" unless callback
346
+
347
+ unless callback.is_a?(Symbol) || callback.is_a?(Proc)
348
+ raise ArgumentError, "#{event} callback must be a Symbol or Proc"
349
+ end
350
+
351
+ callbacks_for(event) << callback
352
+ end
353
+ end
102
354
  end
103
355
  end
@@ -2,15 +2,34 @@
2
2
 
3
3
  require_relative "constants"
4
4
 
5
- # Collection to store arguments and outputs values
6
5
  module Light
7
6
  module Services
7
+ # Collection module for storing argument and output values.
8
8
  module Collection
9
+ # Storage for service arguments or outputs with type validation.
10
+ #
11
+ # @example Accessing values
12
+ # service.arguments[:name] # => "John"
13
+ # service.outputs[:user] # => #<User id: 1>
9
14
  class Base
10
15
  extend Forwardable
11
16
 
17
+ # @!method key?(key)
18
+ # Check if a key exists in the collection.
19
+ # @param key [Symbol] the key to check
20
+ # @return [Boolean] true if key exists
21
+
22
+ # @!method to_h
23
+ # Convert collection to a hash.
24
+ # @return [Hash] the stored values
12
25
  def_delegators :@storage, :key?, :to_h
13
26
 
27
+ # Initialize a new collection.
28
+ #
29
+ # @param instance [Base] the service instance
30
+ # @param collection_type [String] "arguments" or "outputs"
31
+ # @param storage [Hash] initial values
32
+ # @raise [ArgTypeError] if storage is not a Hash
14
33
  def initialize(instance, collection_type, storage = {})
15
34
  validate_collection_type!(collection_type)
16
35
 
@@ -23,22 +42,43 @@ module Light
23
42
  raise Light::Services::ArgTypeError, "#{instance.class} - #{collection_type} must be a Hash"
24
43
  end
25
44
 
45
+ # Set a value in the collection.
46
+ #
47
+ # @param key [Symbol] the key to set
48
+ # @param value [Object] the value to store
49
+ # @return [Object] the stored value
26
50
  def set(key, value)
27
51
  @storage[key] = value
28
52
  end
29
53
 
54
+ # Get a value from the collection.
55
+ #
56
+ # @param key [Symbol] the key to retrieve
57
+ # @return [Object, nil] the stored value or nil
30
58
  def get(key)
31
59
  @storage[key]
32
60
  end
33
61
 
62
+ # Get a value using bracket notation.
63
+ #
64
+ # @param key [Symbol] the key to retrieve
65
+ # @return [Object, nil] the stored value or nil
34
66
  def [](key)
35
67
  get(key)
36
68
  end
37
69
 
70
+ # Set a value using bracket notation.
71
+ #
72
+ # @param key [Symbol] the key to set
73
+ # @param value [Object] the value to store
74
+ # @return [Object] the stored value
38
75
  def []=(key, value)
39
76
  set(key, value)
40
77
  end
41
78
 
79
+ # Load default values for fields that haven't been set.
80
+ #
81
+ # @return [void]
42
82
  def load_defaults
43
83
  settings_collection.each do |name, settings|
44
84
  next if !settings.default_exists || key?(name)
@@ -51,6 +91,10 @@ module Light
51
91
  end
52
92
  end
53
93
 
94
+ # Validate all values against their type definitions.
95
+ #
96
+ # @return [void]
97
+ # @raise [ArgTypeError] if a value fails type validation
54
98
  def validate!
55
99
  settings_collection.each do |name, field|
56
100
  next if field.optional && (!key?(name) || get(name).nil?)
@@ -62,7 +106,11 @@ module Light
62
106
  end
63
107
  end
64
108
 
65
- # Extend args with context values (only for arguments)
109
+ # Extend arguments hash with context values from this collection.
110
+ # Only applies to arguments collections.
111
+ #
112
+ # @param args [Hash] arguments hash to extend
113
+ # @return [Hash] the extended arguments hash
66
114
  def extend_with_context(args)
67
115
  return args unless @collection_type == CollectionTypes::ARGUMENTS
68
116
 
@@ -40,6 +40,9 @@ module Light
40
40
 
41
41
  break if @errors.break? || @warnings.break?
42
42
  end
43
+ rescue Light::Services::StopExecution
44
+ # Gracefully handle stop_immediately! inside transaction to prevent rollback
45
+ @stopped = true
43
46
  end
44
47
  end
45
48
 
@@ -3,17 +3,73 @@
3
3
  module Light
4
4
  module Services
5
5
  class << self
6
+ # Configure Light::Services with a block.
7
+ #
8
+ # @yield [Config] the configuration object
9
+ # @return [void]
10
+ #
11
+ # @example
12
+ # Light::Services.configure do |config|
13
+ # config.require_type = true
14
+ # config.use_transactions = false
15
+ # end
6
16
  def configure
7
17
  yield config
8
18
  end
9
19
 
20
+ # Get the global configuration object.
21
+ #
22
+ # @return [Config] the configuration instance
10
23
  def config
11
24
  @config ||= Config.new
12
25
  end
13
26
  end
14
27
 
28
+ # Configuration class for Light::Services global settings.
29
+ #
30
+ # @example Accessing configuration
31
+ # Light::Services.config.require_type # => true
32
+ #
33
+ # @example Modifying configuration
34
+ # Light::Services.config.use_transactions = false
15
35
  class Config
36
+ # @return [Boolean] whether arguments and outputs must have a type specified
37
+ attr_reader :require_type
38
+
39
+ # @return [Boolean] whether to wrap service execution in a database transaction
40
+ attr_reader :use_transactions
41
+
42
+ # @return [Boolean] whether to copy errors to parent service in chain
43
+ attr_reader :load_errors
44
+
45
+ # @return [Boolean] whether to stop executing steps when an error is added
46
+ attr_reader :break_on_error
47
+
48
+ # @return [Boolean] whether to raise Light::Services::Error when service fails
49
+ attr_reader :raise_on_error
50
+
51
+ # @return [Boolean] whether to rollback the transaction when an error is added
52
+ attr_reader :rollback_on_error
53
+
54
+ # @return [Boolean] whether to copy warnings to parent service in chain
55
+ attr_reader :load_warnings
56
+
57
+ # @return [Boolean] whether to stop executing steps when a warning is added
58
+ attr_reader :break_on_warning
59
+
60
+ # @return [Boolean] whether to raise Light::Services::Error when service has warnings
61
+ attr_reader :raise_on_warning
62
+
63
+ # @return [Boolean] whether to rollback the transaction when a warning is added
64
+ attr_reader :rollback_on_warning
65
+
66
+ # @return [Hash{String => String}] custom type mappings for Ruby LSP addon.
67
+ # Maps dry-types or custom types to Ruby types for hover/completion.
68
+ # @example { "Types::UUID" => "String", "CustomTypes::Money" => "BigDecimal" }
69
+ attr_reader :ruby_lsp_type_mappings
70
+
16
71
  DEFAULTS = {
72
+ require_type: true,
17
73
  use_transactions: true,
18
74
 
19
75
  load_errors: true,
@@ -25,22 +81,46 @@ module Light
25
81
  break_on_warning: false,
26
82
  raise_on_warning: false,
27
83
  rollback_on_warning: false,
84
+
85
+ ruby_lsp_type_mappings: {}.freeze,
28
86
  }.freeze
29
87
 
30
- attr_accessor(*DEFAULTS.keys)
88
+ DEFAULTS.each_key do |name|
89
+ define_method(:"#{name}=") do |value|
90
+ instance_variable_set(:"@#{name}", value)
91
+ @to_h = nil # Invalidate memoized hash
92
+ end
93
+ end
31
94
 
95
+ # Initialize configuration with default values.
32
96
  def initialize
33
97
  reset_to_defaults!
34
98
  end
35
99
 
100
+ # Reset all configuration options to their default values.
101
+ #
102
+ # @return [void]
36
103
  def reset_to_defaults!
37
- DEFAULTS.each { |key, value| public_send(:"#{key}=", value) }
104
+ DEFAULTS.each do |key, value|
105
+ instance_variable_set(:"@#{key}", value)
106
+ end
107
+
108
+ @to_h = nil # Invalidate memoized hash
38
109
  end
39
110
 
111
+ # Convert configuration to a hash.
112
+ #
113
+ # @return [Hash{Symbol => Object}] all configuration options as a hash
40
114
  def to_h
41
- DEFAULTS.keys.to_h { |key| [key, public_send(key)] }
115
+ @to_h ||= DEFAULTS.keys.to_h do |key|
116
+ [key, public_send(key)]
117
+ end
42
118
  end
43
119
 
120
+ # Merge configuration with additional options.
121
+ #
122
+ # @param config [Hash] options to merge
123
+ # @return [Hash] merged configuration hash
44
124
  def merge(config)
45
125
  to_h.merge(config)
46
126
  end
@@ -31,6 +31,9 @@ module Light
31
31
  :failed?,
32
32
  :errors?,
33
33
  :warnings?,
34
+ :stop!,
35
+ :stopped?,
36
+ :stop_immediately!,
34
37
  :done!,
35
38
  :done?,
36
39
  :call,
@@ -41,6 +41,7 @@ module Light
41
41
  Validation.validate_symbol_name!(name, :argument, self)
42
42
  Validation.validate_reserved_name!(name, :argument, self)
43
43
  Validation.validate_name_conflicts!(name, :argument, self)
44
+ Validation.validate_type_required!(name, :argument, self, opts)
44
45
 
45
46
  own_arguments[name] = Settings::Field.new(name, self, opts.merge(field_type: FieldTypes::ARGUMENT))
46
47
  @arguments = nil # Clear memoized arguments since we're modifying them
@@ -37,6 +37,7 @@ module Light
37
37
  Validation.validate_symbol_name!(name, :output, self)
38
38
  Validation.validate_reserved_name!(name, :output, self)
39
39
  Validation.validate_name_conflicts!(name, :output, self)
40
+ Validation.validate_type_required!(name, :output, self, opts)
40
41
 
41
42
  own_outputs[name] = Settings::Field.new(name, self, opts.merge(field_type: FieldTypes::OUTPUT))
42
43
  @outputs = nil # Clear memoized outputs since we're modifying them