servus 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e468137fc40c9d2f18214dea55b9c3fc6211e5479e73f5b0917ed168d116a2f
4
- data.tar.gz: 5390b065cb3478d837cbd90047a7f8facaaf33273b0e2aa4d459167343354d8d
3
+ metadata.gz: 2f382ed3d92dd577c93965ae207c00d28cac6dfddf452cf22fb5dafcd69d56eb
4
+ data.tar.gz: f5a4394a33f5a3061d3c5746fdd058eb457cb29baa2f0adb9cc97c0679f5c158
5
5
  SHA512:
6
- metadata.gz: 20e5b65a0cd82207a0c494b76e21d0562ce41ca27394b418e7d6921b841c4f47d4f8384f157146195eea713f1af3d28d09b81c210fd7f8a5041b13f8464d66a2
7
- data.tar.gz: 52f7ca40905dc890fa6850cf87b8394471a94607749457aaeb4784c4524315fd769e40daa4322aae12b5bd84eb9282524a90836bab8a31ab9abfe01d021211a7
6
+ metadata.gz: 5ccb1adc1b0bd4b7ff9f8b754571be541bbf7e3b9246746fe73c9e1259ad4b6f6e3a5a4d6c07d3b9f30cb8ddda94dfc2cad7c78fb6dc31fb17440c1d3a19b6be
7
+ data.tar.gz: 0107e7e4d770913a5e86de5c496791e6311c913dc6b2bdad03417b78a2e66be6d50c2fc5d213f87b51162ac4a15d3c6c9dcb9b323267a9db9d0b222099f60245
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servus
4
+ module Generators
5
+ # Rails generator for creating Servus event classes.
6
+ #
7
+ # Generates an event class and spec file. The event name is inferred
8
+ # from the class name — no explicit +event_name+ call needed.
9
+ #
10
+ # @example Generate an event
11
+ # rails g servus:event referral_verified
12
+ #
13
+ # @example Generated files
14
+ # app/events/referral_verified.rb
15
+ # spec/app/events/referral_verified_spec.rb
16
+ #
17
+ # @see https://guides.rubyonrails.org/generators.html
18
+ class EventGenerator < Rails::Generators::NamedBase
19
+ source_root File.expand_path('templates', __dir__)
20
+
21
+ class_option :no_docs, type: :boolean,
22
+ default: false,
23
+ desc: 'Skip documentation comments in generated files'
24
+
25
+ # Creates the event class and spec files.
26
+ #
27
+ # @return [void]
28
+ def create_event_file
29
+ template 'event.rb.erb', event_path
30
+ template 'event_spec.rb.erb', event_spec_path
31
+ end
32
+
33
+ private
34
+
35
+ # @return [String] event file path
36
+ # @api private
37
+ def event_path
38
+ File.join(Servus.config.events_dir, "#{file_name}_event.rb")
39
+ end
40
+
41
+ # @return [String] spec file path
42
+ # @api private
43
+ def event_spec_path
44
+ File.join(Servus.config.tests_dir, Servus.config.events_dir, "#{file_name}_event_spec.rb")
45
+ end
46
+
47
+ # @return [String] event class name (e.g. "ReferralVerifiedEvent")
48
+ # @api private
49
+ def event_class_name
50
+ "#{class_name}Event"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ <%- unless options[:no_docs] -%>
4
+ # Defines the :<%= file_name %> event.
5
+ #
6
+ # Event classes declare the contract (schema) for an event and optionally
7
+ # wire up service invocations that run when the event fires. The event
8
+ # name is inferred from the class name.
9
+ #
10
+ # @example Emit this event from anywhere
11
+ # <%= event_class_name %>.emit({ user_id: 123 })
12
+ #
13
+ # @example Invoke a service when this event fires
14
+ # invoke SendEmail::Service, async: true do |payload|
15
+ # { user_id: payload[:user_id] }
16
+ # end
17
+ #
18
+ # @example Pass full payload through (no mapper block)
19
+ # invoke AuditLogger::Service, async: true
20
+ #
21
+ # @example Conditional invocation
22
+ # invoke GrantRewards::Service, if: ->(payload) { payload[:premium] } do |payload|
23
+ # { user_id: payload[:user_id] }
24
+ # end
25
+ #
26
+ # Available options for `invoke`:
27
+ # - async: true - Invoke service asynchronously via ActiveJob
28
+ # - queue: :queue_name - Specify ActiveJob queue (requires async: true)
29
+ # - if: ->(payload) {} - Condition that must be true to invoke
30
+ # - unless: ->(payload) {} - Condition that must be false to invoke
31
+ #
32
+ # @see Servus::Event
33
+ # @see Servus::Events::Bus
34
+ <%- end -%>
35
+ class <%= event_class_name %> < Servus::Event
36
+ schema payload: {
37
+ type: 'object',
38
+ description: '<%= event_class_name %> event payload',
39
+ }
40
+
41
+ # invoke YourService, async: true do |payload|
42
+ # { example_arg: payload[:example_field] }
43
+ # end
44
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe <%= event_class_name %> do
6
+ let(:payload) do
7
+ {
8
+ # TODO: Add sample payload fields
9
+ }
10
+ end
11
+
12
+ <%- unless options[:no_docs] -%>
13
+ # TODO: Add tests for service invocations
14
+ # it 'invokes YourService with mapped arguments' do
15
+ # expect { described_class.emit(payload) }
16
+ # .to emit_event(:<%= file_name %>)
17
+ # .with(hash_including(expected_field: 'value'))
18
+ # end
19
+ <%- end -%>
20
+ end
data/lib/servus/config.rb CHANGED
@@ -22,7 +22,7 @@ module Servus
22
22
  # @return [String] the schemas directory path
23
23
  attr_accessor :schemas_dir
24
24
 
25
- # The directory where event handlers are located.
25
+ # The directory where Event classes are located.
26
26
  #
27
27
  # Defaults to `Rails.root/app/events` in Rails applications.
28
28
  #
@@ -36,14 +36,6 @@ module Servus
36
36
  # @return [String] the services directory path
37
37
  attr_accessor :services_dir
38
38
 
39
- # Whether to validate that all event handlers subscribe to events that are actually emitted by services.
40
- #
41
- # When enabled, raises an error on boot if handlers subscribe to non-existent events.
42
- # Helps catch typos and orphaned handlers.
43
- #
44
- # @return [Boolean] true to validate, false to skip validation
45
- attr_accessor :strict_event_validation
46
-
47
39
  # The directory where guard classes are located.
48
40
  #
49
41
  # Defaults to `Rails.root/app/guards` in Rails applications.
@@ -82,14 +74,28 @@ module Servus
82
74
  # @return [Boolean] true to require result schemas, false to allow schema-less services
83
75
  attr_accessor :require_service_result_schema
84
76
 
85
- # Whether to require all event handlers to define a payload schema.
77
+ # Whether to require all event classes to define a payload schema.
86
78
  #
87
79
  # When enabled, raises {Servus::Support::Errors::SchemaRequiredError} when
88
- # an event handler validates a payload without a payload schema defined.
80
+ # an event validates a payload without a payload schema defined.
89
81
  #
90
- # @return [Boolean] true to require payload schemas, false to allow schema-less handlers
82
+ # @return [Boolean] true to require payload schemas, false to allow schema-less events
91
83
  attr_accessor :require_event_payload_schema
92
84
 
85
+ # The ordered list of routers that resolve invocations for events.
86
+ #
87
+ # The Bus iterates routers in order, collects invocations, deduplicates
88
+ # by key (first wins), and executes. Defaults to +[ClassRouter.new]+
89
+ # which reads +invoke+ declarations from Event classes.
90
+ #
91
+ # @return [Array<Servus::Events::Router>]
92
+ attr_writer :routers
93
+
94
+ # @return [Array<Servus::Events::Router>]
95
+ def routers
96
+ @routers || [Servus::Events::ClassRouter.new]
97
+ end
98
+
93
99
  # Whether external instantiation of services is blocked and instance
94
100
  # `#call` methods are automatically privatized.
95
101
  #
@@ -121,7 +127,6 @@ module Servus
121
127
  # @api private
122
128
  def initialize
123
129
  set_default_directories
124
- @strict_event_validation = true
125
130
  @include_default_guards = true
126
131
  @lockdown_enabled = true
127
132
  @require_service_arguments_schema = false
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servus
4
+ # Base class for event definitions.
5
+ #
6
+ # Event classes live in app/events/ and serve three purposes:
7
+ #
8
+ # 1. *Contract* — declares the event exists and defines its name
9
+ # 2. *Validator* — schema enforcement on any emission
10
+ # 3. *Declarative routing* — optional +invoke+ declarations
11
+ #
12
+ # The event name can be set explicitly via +event_name+ or inferred
13
+ # from the class name (e.g. +OrderPlaced+ becomes +:order_placed+).
14
+ # Call +ensure_registered!+ to trigger inference for classes that
15
+ # don't declare an explicit name.
16
+ #
17
+ # @example Event with explicit name and invoke declarations
18
+ # class UserCreated < Servus::Event
19
+ # event_name :user_created
20
+ #
21
+ # schema payload: { type: 'object', required: ['user_id'] }
22
+ #
23
+ # invoke SendWelcomeEmail::Service, async: true do |payload|
24
+ # { user_id: payload[:user_id] }
25
+ # end
26
+ # end
27
+ #
28
+ # @example Event with inferred name (no invoke — schema-only contract)
29
+ # class OrderPlaced < Servus::Event
30
+ # schema payload: { type: 'object', required: ['order_id'] }
31
+ # end
32
+ #
33
+ # @example Event that passes full payload through (no mapper block)
34
+ # class AuditLogCreated < Servus::Event
35
+ # event_name :audit_log_created
36
+ #
37
+ # invoke AuditLogger::Service, async: true
38
+ # end
39
+ #
40
+ # @see Servus::Events::Bus
41
+ # @see Servus::Events::Router
42
+ # @see Servus::Base
43
+ class Event
44
+ class << self
45
+ # Declares or returns the event name.
46
+ #
47
+ # When called with an argument, sets the event name and registers
48
+ # with the Bus. When called without arguments, returns the current
49
+ # event name.
50
+ #
51
+ # If never called explicitly, use +ensure_registered!+ to infer
52
+ # the name from the class name.
53
+ #
54
+ # @overload event_name(name)
55
+ # @param name [Symbol] the event name to register
56
+ # @return [void]
57
+ # @raise [RuntimeError] if called twice with different names
58
+ #
59
+ # @overload event_name
60
+ # @return [Symbol, nil] the event name or nil if not configured
61
+ #
62
+ # @example Explicit name
63
+ # class UserCreated < Servus::Event
64
+ # event_name :user_created
65
+ # end
66
+ #
67
+ # @example Inferred name (via ensure_registered!)
68
+ # class OrderPlaced < Servus::Event; end
69
+ # OrderPlaced.ensure_registered!
70
+ # OrderPlaced.event_name # => :order_placed
71
+ def event_name(name = nil)
72
+ return @event_name if name.nil?
73
+
74
+ raise "Event already subscribed to :#{@event_name}. Cannot subscribe to :#{name}" if @event_name
75
+
76
+ @event_name = name
77
+ Servus::Events::Bus.register_event(name, self)
78
+ end
79
+
80
+ # Infers and registers the event name from the class name if not
81
+ # already set explicitly. Safe to call multiple times — does
82
+ # nothing if already registered. Skips anonymous classes.
83
+ #
84
+ # @return [void]
85
+ def ensure_registered!
86
+ return if @event_name
87
+ return if name.nil?
88
+
89
+ event_name(name.demodulize.underscore.to_sym)
90
+ end
91
+
92
+ # Declares a service invocation in response to the event.
93
+ #
94
+ # Multiple invocations can be declared for a single event. Each invocation
95
+ # requires a block that maps the event payload to the service's arguments.
96
+ #
97
+ # @param service_class [Class] the service class to invoke (must inherit from Servus::Base)
98
+ # @param options [Hash] invocation options
99
+ # @option options [Boolean] :async invoke the service asynchronously via call_async
100
+ # @option options [Symbol] :queue the queue name for async jobs
101
+ # @option options [Proc] :if condition that must return true for invocation
102
+ # @option options [Proc] :unless condition that must return false for invocation
103
+ # @yield [payload] block that maps event payload to service arguments
104
+ # @yieldparam payload [Hash] the event payload
105
+ # @yieldreturn [Hash] keyword arguments for the service's initialize method
106
+ # @return [void]
107
+ #
108
+ # @example Basic invocation
109
+ # invoke SendEmail::Service do |payload|
110
+ # { user_id: payload[:user_id], email: payload[:email] }
111
+ # end
112
+ #
113
+ # @example Async invocation with queue
114
+ # invoke SendEmail::Service, async: true, queue: :mailers do |payload|
115
+ # { user_id: payload[:user_id] }
116
+ # end
117
+ #
118
+ # @example Conditional invocation
119
+ # invoke GrantRewards::Service, if: ->(p) { p[:premium] } do |payload|
120
+ # { user_id: payload[:user_id] }
121
+ # end
122
+ def invoke(service_class, options = {}, &block)
123
+ @invocations ||= []
124
+ @invocations << {
125
+ service_class: service_class,
126
+ options: options,
127
+ mapper: block || ->(payload) { payload }
128
+ }
129
+ end
130
+
131
+ # Returns all service invocations declared for this event.
132
+ #
133
+ # @return [Array<Hash>] array of invocation configurations
134
+ def invocations
135
+ @invocations || []
136
+ end
137
+
138
+ # Defines the JSON schema for validating event payloads.
139
+ #
140
+ # @param payload [Hash, nil] JSON schema for validating event payloads
141
+ # @return [void]
142
+ #
143
+ # @example
144
+ # class UserCreated < Servus::Event
145
+ # event_name :user_created
146
+ #
147
+ # schema payload: {
148
+ # type: 'object',
149
+ # required: ['user_id', 'email'],
150
+ # properties: {
151
+ # user_id: { type: 'integer' },
152
+ # email: { type: 'string', format: 'email' }
153
+ # }
154
+ # }
155
+ # end
156
+ def schema(payload: nil)
157
+ @payload_schema = payload.with_indifferent_access if payload
158
+ end
159
+
160
+ # Returns the payload schema.
161
+ #
162
+ # @return [Hash, nil] the payload schema or nil if not defined
163
+ # @api private
164
+ attr_reader :payload_schema
165
+
166
+ # Emits this event via the Bus.
167
+ #
168
+ # Provides a type-safe, discoverable way to emit events from anywhere in
169
+ # the application (controllers, jobs, rake tasks) without creating a service.
170
+ #
171
+ # @param payload [Hash] the event payload
172
+ # @return [void]
173
+ # @raise [RuntimeError] if no event name configured
174
+ #
175
+ # @example Emit from controller
176
+ # class UsersController
177
+ # def create
178
+ # user = User.create!(params)
179
+ # UserCreated.emit({ user_id: user.id, email: user.email })
180
+ # redirect_to user
181
+ # end
182
+ # end
183
+ #
184
+ # @example Emit from background job
185
+ # class ProcessDataJob
186
+ # def perform(data_id)
187
+ # result = process_data(data_id)
188
+ # DataProcessed.emit({ data_id: data_id, status: result })
189
+ # end
190
+ # end
191
+ def emit(payload)
192
+ raise 'No event configured. Call event_name :name first.' unless @event_name
193
+
194
+ Servus::Support::Validator.validate_event_payload!(self, payload)
195
+
196
+ Servus::Events::Bus.emit(@event_name, payload)
197
+ end
198
+
199
+ # Returns Invocation objects for the given payload, with conditions
200
+ # already evaluated. This is what routers call to resolve actions.
201
+ #
202
+ # @param payload [Hash] the event payload
203
+ # @return [Array<Servus::Events::Invocation>] invocations that passed conditions
204
+ def invocations_for(payload)
205
+ invocations.filter_map do |inv|
206
+ next unless should_invoke?(payload, inv[:options])
207
+
208
+ Servus::Events::Invocation.new(
209
+ service: inv[:service_class],
210
+ params: inv[:mapper].call(payload),
211
+ options: inv[:options].except(:if, :unless)
212
+ )
213
+ end
214
+ end
215
+
216
+ # Handles an event by resolving and executing all invocations.
217
+ #
218
+ # @param payload [Hash] the event payload
219
+ # @return [Array] results from all invoked services
220
+ def handle(payload)
221
+ invocations_for(payload).map(&:execute)
222
+ end
223
+
224
+ private
225
+
226
+ # @api private
227
+ def should_invoke?(payload, options)
228
+ return false if options[:if] && !options[:if].call(payload)
229
+ return false if options[:unless]&.call(payload)
230
+
231
+ true
232
+ end
233
+ end
234
+ end
235
+ end
@@ -2,93 +2,89 @@
2
2
 
3
3
  module Servus
4
4
  module Events
5
- # Thread-safe event bus for registering and dispatching event handlers.
5
+ # Central event bus for registering Event classes and dispatching
6
+ # events through configured routers.
6
7
  #
7
- # The Bus acts as a central registry that maps event names to their
8
- # corresponding handler classes. It uses ActiveSupport::Notifications
9
- # internally to provide instrumentation and thread-safe event dispatch.
8
+ # The Bus maintains a registry mapping event names to their Event
9
+ # class definitions (one-to-one). On +emit+, it delegates to the
10
+ # configured routers to resolve invocations, deduplicates by key,
11
+ # and executes. ActiveSupport::Notifications wraps the dispatch
12
+ # cycle for the +subscribe_all+ hook (e.g. forwarding to Eventus).
10
13
  #
11
- # Events are automatically instrumented and will appear in Rails logs
12
- # with timing information, making it easy to monitor event performance.
13
- #
14
- # @example Registering a handler
15
- # class UserCreatedHandler < Servus::EventHandler
16
- # handles :user_created
14
+ # @example Registering an event class
15
+ # class UserCreated < Servus::Event
16
+ # event_name :user_created
17
17
  # end
18
+ # # Registration happens automatically via the event_name DSL
18
19
  #
19
- # Servus::Events::Bus.register_handler(:user_created, UserCreatedHandler)
20
- #
21
- # @example Retrieving handlers for an event
22
- # handlers = Servus::Events::Bus.handlers_for(:user_created)
23
- # handlers.each { |handler| handler.handle(payload) }
20
+ # @example Emitting an event
21
+ # Bus.emit(:user_created, { user_id: 123 })
24
22
  #
25
- # @example Instrumentation in logs
26
- # Bus.emit(:user_created, user_id: 123)
27
- # # Rails log: servus.events.user_created (1.2ms) {:user_id=>123}
23
+ # @example Forwarding all events to an external system
24
+ # Bus.subscribe_all do |event_name, payload, started_at:, **|
25
+ # ExternalForwarder.perform_later(event: event_name, payload:)
26
+ # end
28
27
  #
29
- # @see Servus::EventHandler
28
+ # @see Servus::Event
29
+ # @see Servus::Events::Router
30
+ # @see Servus::Events::ClassRouter
30
31
  class Bus
31
32
  class << self
32
- # Registers a handler class for a specific event.
33
+ # Registers an Event class for a specific event name.
33
34
  #
34
- # Multiple handlers can be registered for the same event, and they
35
- # will all be invoked when the event is emitted. The handler is
36
- # automatically subscribed to ActiveSupport::Notifications.
35
+ # Each event name maps to exactly one Event class. Attempting to
36
+ # register a second class for the same name raises an error.
37
37
  #
38
- # Handlers are typically registered automatically when EventHandler
39
- # classes are loaded at boot time via the `handles` DSL method.
38
+ # Event classes are typically registered automatically at boot time
39
+ # via the +event_name+ DSL method or +ensure_registered!+.
40
40
  #
41
- # @param event_name [Symbol] the name of the event
42
- # @param handler_class [Class] the handler class to register
43
- # @return [Array] the updated array of handlers for this event
41
+ # @param name [Symbol] the event name
42
+ # @param event_class [Class] the Event subclass to register
43
+ # @return [void]
44
+ # @raise [RuntimeError] if the event name is already registered
44
45
  #
45
46
  # @example
46
- # Bus.register_handler(:user_created, UserCreatedHandler)
47
- def register_handler(event_name, handler_class)
48
- handlers[event_name] ||= []
49
- handlers[event_name] << handler_class
50
-
51
- # Subscribe to ActiveSupport::Notifications
52
- subscription = ActiveSupport::Notifications.subscribe(notification_name(event_name)) do |*args|
53
- event = ActiveSupport::Notifications::Event.new(*args)
54
- handler_class.handle(event.payload)
47
+ # Bus.register_event(:user_created, UserCreated)
48
+ def register_event(name, event_class)
49
+ if events.key?(name)
50
+ raise "Event :#{name} is already registered to #{events[name]}. Cannot register #{event_class}"
55
51
  end
56
52
 
57
- # Store subscription for cleanup
58
- subscriptions[event_name] ||= []
59
- subscriptions[event_name] << subscription
53
+ events[name] = event_class
60
54
  end
61
55
 
62
- # Retrieves all registered handlers for a specific event.
56
+ # Returns the Event class registered for the given name.
63
57
  #
64
- # Returns a duplicate array to prevent external modification of the
65
- # internal handler registry.
66
- #
67
- # @param event_name [Symbol] the name of the event
68
- # @return [Array<Class>] array of handler classes registered for this event
58
+ # @param name [Symbol] the event name
59
+ # @return [Class, nil] the Event class or nil if not registered
69
60
  #
70
61
  # @example
71
- # handlers = Bus.handlers_for(:user_created)
72
- # handlers.each { |handler| handler.handle(payload) }
73
- def handlers_for(event_name)
74
- (handlers[event_name] || []).dup
62
+ # event_class = Bus.event_for(:user_created)
63
+ # event_class.invocations_for(payload)
64
+ def event_for(name)
65
+ events[name]
75
66
  end
76
67
 
77
- # Emits an event to all registered handlers with instrumentation.
68
+ # Emits an event through the configured routers.
78
69
  #
79
- # Uses ActiveSupport::Notifications to instrument the event, providing
80
- # automatic timing and logging. The event will appear in Rails logs
81
- # with duration and payload information.
70
+ # Collects invocations from all routers (in config array order),
71
+ # deduplicates by key (first wins), and executes each. The entire
72
+ # dispatch is wrapped in ActiveSupport::Notifications so that
73
+ # +subscribe_all+ listeners receive timing information.
82
74
  #
83
75
  # @param event_name [Symbol] the name of the event to emit
84
- # @param payload [Hash] the event payload to pass to handlers
76
+ # @param payload [Hash] the event payload
85
77
  # @return [void]
86
78
  #
87
79
  # @example
88
80
  # Bus.emit(:user_created, { user_id: 123, email: 'user@example.com' })
89
81
  # # Rails log: servus.events.user_created (1.2ms) {:user_id=>123, :email=>"user@example.com"}
90
82
  def emit(event_name, payload)
91
- ActiveSupport::Notifications.instrument(notification_name(event_name), payload)
83
+ ActiveSupport::Notifications.instrument(notification_name(event_name), payload) do
84
+ resolve_invocations(event_name, payload)
85
+ .uniq(&:key)
86
+ .each(&:execute)
87
+ end
92
88
  end
93
89
 
94
90
  # Subscribes to all Servus event emissions.
@@ -120,7 +116,26 @@ module Servus
120
116
  end
121
117
  end
122
118
 
123
- # Clears all registered handlers and unsubscribes from notifications.
119
+ # Installs the internal event logger. Called once at boot via
120
+ # the Railtie. Logs every event emission with its AS::Notifications
121
+ # correlation ID and dispatch duration.
122
+ #
123
+ # Multiple +subscribe_all+ subscriptions coexist — the app can
124
+ # add its own (e.g. for Eventus forwarding) independently.
125
+ #
126
+ # @return [void]
127
+ def enable_logging!
128
+ return if @logging_enabled
129
+
130
+ subscribe_all do |event_name, payload, **meta|
131
+ duration_ms = (meta[:finished_at] - meta[:started_at]) * 1000
132
+ Servus::Support::Logger.log_event(event_name, payload, event_id: meta[:id], duration_ms:)
133
+ end
134
+
135
+ @logging_enabled = true
136
+ end
137
+
138
+ # Clears all registered events.
124
139
  #
125
140
  # Useful for testing and development mode reloading.
126
141
  #
@@ -129,28 +144,23 @@ module Servus
129
144
  # @example
130
145
  # Bus.clear
131
146
  def clear
132
- subscriptions.values.flatten.each do |subscription|
133
- ActiveSupport::Notifications.unsubscribe(subscription)
134
- end
135
-
136
- @handlers = nil
137
- @subscriptions = nil
147
+ @events = nil
138
148
  end
139
149
 
140
150
  private
141
151
 
142
- # Hash storing event handlers.
152
+ # Collects invocations from all configured routers.
143
153
  #
144
- # @return [Hash] hash mapping event names to handler arrays
145
- def handlers
146
- @handlers ||= {}
154
+ # @param event_name [Symbol] the event name
155
+ # @param payload [Hash] the event payload
156
+ # @return [Array<Servus::Events::Invocation>]
157
+ def resolve_invocations(event_name, payload)
158
+ Servus.config.routers.flat_map { |router| router.resolve(event_name, payload) }
147
159
  end
148
160
 
149
- # Hash storing ActiveSupport::Notifications subscriptions.
150
- #
151
- # @return [Hash] hash mapping event names to subscription objects
152
- def subscriptions
153
- @subscriptions ||= {}
161
+ # @return [Hash{Symbol => Class}] event name to Event class mapping
162
+ def events
163
+ @events ||= {}
154
164
  end
155
165
 
156
166
  # Converts an event name to a namespaced notification name.