realm-core 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ class EventRouter
5
+ class Gateway
6
+ def self.auto_register_on_init
7
+ false
8
+ end
9
+
10
+ def initialize(event_factory:, namespace: :default, runtime: nil, **)
11
+ @namespace = namespace
12
+ @event_factory = event_factory
13
+ @runtime = runtime
14
+ end
15
+
16
+ def register(handler_class)
17
+ handler_class.event_types.each do |event_type|
18
+ add_listener(event_type, handler_class.bind_runtime(@runtime))
19
+ end
20
+ end
21
+
22
+ def add_listener(event_type, listener)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def trigger(event_type, attributes = {})
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def worker(*)
31
+ nil
32
+ end
33
+
34
+ def cleanup
35
+ # do nothing
36
+ end
37
+
38
+ def queues
39
+ []
40
+ end
41
+
42
+ protected
43
+
44
+ def create_event(event_type, attributes = {})
45
+ @event_factory.create_event(event_type, **attributes)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './gateway'
4
+
5
+ module Realm
6
+ class EventRouter
7
+ class InternalLoopGateway < Gateway
8
+ def self.auto_register_on_init
9
+ true
10
+ end
11
+
12
+ def initialize(isolated: false, **)
13
+ super
14
+ @listener_map = {}
15
+ @isolated = isolated
16
+ gateways << self
17
+ end
18
+
19
+ def add_listener(event_type, listener)
20
+ (@listener_map[event_type] ||= []) << listener
21
+ end
22
+
23
+ def trigger(event_type, attributes = {})
24
+ create_event(event_type, attributes).tap do |event|
25
+ gateways.each { |gateway| gateway.handle(event_type, event) }
26
+ end
27
+ end
28
+
29
+ def purge!
30
+ gateways.clear
31
+ end
32
+
33
+ protected
34
+
35
+ def handle(event_type, event)
36
+ find_listeners(event_type).each { |listener| listener.(event) }
37
+ end
38
+
39
+ private
40
+
41
+ def find_listeners(event_type)
42
+ @listener_map.fetch_values(event_type, :any) { [] }.flatten
43
+ end
44
+
45
+ def gateways
46
+ @isolated ? (@gateways ||= []) : (@@gateways ||= []) # rubocop:disable Style/ClassVars
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ module Realm
6
+ class HealthStatus
7
+ CODES = %i[green yellow red].freeze
8
+ attr_reader :code, :issues
9
+
10
+ class << self
11
+ def [](code, *issues)
12
+ new(code, issues.flatten)
13
+ end
14
+
15
+ def from_issues(issues)
16
+ new(issues.blank? ? :green : :red, issues)
17
+ end
18
+
19
+ def combine(component_map)
20
+ code_index = component_map.values.map { |i| CODES.index(i.code) }.max
21
+ new(CODES[code_index || 0], [], component_map)
22
+ end
23
+ end
24
+
25
+ def for_component(*names)
26
+ @component_map.dig(*names)
27
+ end
28
+
29
+ def to_h
30
+ hash = { status: @code }
31
+ hash[:issues] = @issues if @issues.present?
32
+ hash[:components] = @component_map.transform_values(&:to_h) if @component_map.present?
33
+ hash
34
+ end
35
+
36
+ private
37
+
38
+ def initialize(code, issues = [], component_map = {})
39
+ raise ArgumentError, "Invalid status code #{code}" unless CODES.include?(code)
40
+
41
+ @code = code
42
+ @issues = issues.freeze
43
+ @component_map = component_map.freeze
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ module Mixins
5
+ module AggregateMember
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ def aggregate
11
+ self.class.aggregate
12
+ end
13
+
14
+ module ClassMethods
15
+ def aggregate
16
+ @aggregate ||= begin
17
+ module_chain = name.split('::')
18
+ domain_index = module_chain.index('Domain')
19
+ domain_index && module_chain[domain_index + 1].underscore.to_sym
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'realm/error'
4
+
5
+ module Realm
6
+ module Mixins
7
+ module ContextInjection
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ base.prepend(Initializer)
11
+ end
12
+
13
+ module ClassMethods
14
+ def inject(*names, &block)
15
+ names.each do |name|
16
+ define_method(name) do
17
+ raise Realm::DependencyMissing, name unless context.key?(name)
18
+
19
+ return context[name] unless block
20
+
21
+ var = "@#{name}"
22
+ return instance_variable_get(var) if instance_variable_defined?(var)
23
+
24
+ instance_variable_set(var, block.(context[name]))
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ module Initializer
31
+ def initialize(*args, context: nil, **kwargs)
32
+ @context = context || context_from_root_module || {}
33
+ super(*args, **kwargs)
34
+ end
35
+
36
+ private
37
+
38
+ def context_from_root_module
39
+ root_module = self.class.module_parents[-2]
40
+ root_module.realm.context if root_module.respond_to?(:realm)
41
+ end
42
+ end
43
+
44
+ protected
45
+
46
+ attr_reader :context
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/introspection'
4
+ require 'active_support/core_ext/class/attribute'
5
+
6
+ module Realm
7
+ module Mixins
8
+ module Controller
9
+ def self.included(base)
10
+ base.class_attribute(:aggregate_name)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ def domain_runtime
15
+ @domain_runtime ||= root_domain_runtime.session(domain_context)
16
+ end
17
+
18
+ def domain_context
19
+ {}
20
+ end
21
+
22
+ def query(identifier, params = {})
23
+ domain_runtime.query(get_dispatchable(identifier), params)
24
+ end
25
+
26
+ def run(identifier, params = {})
27
+ domain_runtime.run(get_dispatchable(identifier), params)
28
+ end
29
+
30
+ def run_as_job(identifier, params = {})
31
+ domain_runtime.run_as_job(get_dispatchable(identifier), params)
32
+ end
33
+
34
+ private
35
+
36
+ def get_dispatchable(identifier)
37
+ return identifier if identifier.respond_to?(:call)
38
+
39
+ [aggregate_name, identifier].compact.join('.')
40
+ end
41
+
42
+ def root_domain_runtime
43
+ self.class.module_parents[-2].realm
44
+ end
45
+
46
+ module ClassMethods
47
+ def with_aggregate(name)
48
+ self.aggregate_name = name
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ module Mixins
5
+ module Decorator
6
+ def self.[](decorated) # rubocop:disable Metrics/MethodLength
7
+ Module.new do
8
+ def method_missing(...)
9
+ _decorated.send(...)
10
+ end
11
+
12
+ def respond_to_missing?(...)
13
+ _decorated.respond_to?(...)
14
+ end
15
+
16
+ if decorated.to_s[0] == '@'
17
+ define_method :initialize do |value|
18
+ instance_variable_set(decorated, value)
19
+ end
20
+
21
+ define_method :_decorated do
22
+ instance_variable_get(decorated)
23
+ end
24
+ else
25
+ define_method :_decorated do
26
+ send(decorated)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'realm/dependency'
4
+
5
+ module Realm
6
+ module Mixins
7
+ module DependencyInjection
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def new(*args, **kwargs, &block)
14
+ instance = allocate
15
+ deps.each { |d| define_dependency_method(instance, kwargs, d) }
16
+ kwargs_without_dependencies = kwargs.reject { |k, _| deps.any? { |d| d.name == k } }
17
+ instance.send(:initialize, *args, **kwargs_without_dependencies, &block)
18
+ instance
19
+ end
20
+
21
+ def inject(*dependables, **options)
22
+ deps.concat(dependables.map { |d| Dependency.new(d, **options) })
23
+ end
24
+
25
+ def dependencies
26
+ deps.freeze
27
+ end
28
+
29
+ private
30
+
31
+ def deps
32
+ @deps ||= []
33
+ end
34
+
35
+ def define_dependency_method(instance, kwargs, spec)
36
+ dependency = kwargs[spec.name]
37
+ instance.singleton_class.class_eval do
38
+ define_method(spec.name) do
39
+ return dependency unless spec.lazy?
40
+
41
+ var = "@#{spec.name}"
42
+ return instance_variable_get(var) if instance_variable_defined?(var)
43
+
44
+ instance_variable_set(var, dependency.respond_to?(:call) ? dependency.call : dependency)
45
+ end
46
+ protected spec.name
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'realm/error'
4
+
5
+ module Realm
6
+ module Mixins
7
+ module Reactive
8
+ protected
9
+
10
+ def run(command, params = {})
11
+ parts = command.to_s.split('.')
12
+ parts.prepend(aggregate) if parts.size == 1 && respond_to?(:aggregate) && aggregate
13
+ @runtime.run(parts.join('.'), params.to_h)
14
+ end
15
+
16
+ def trigger(event_type, attributes = {})
17
+ attributes = attributes.to_h
18
+ head = { origin: origin(caller_locations(1, 1)) }.merge(attributes.fetch(:head, {}))
19
+ final_attrs = attributes.merge(head: head)
20
+ final_attrs[:cause] ||= context[:cause] if context.key?(:cause)
21
+ @runtime.trigger(event_type, final_attrs)
22
+ end
23
+
24
+ private
25
+
26
+ # Detects the class and method from which this event is triggered
27
+ def origin(backtrace)
28
+ [self.class.name, backtrace[0].to_s.match(/`([^']+)'/)&.then { |m| "##{m[1]}" }].join
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'realm/error'
4
+
5
+ module Realm
6
+ module Mixins
7
+ module RepositoryHelper
8
+ class OnlyOneWriteRepo < Realm::Error['You can have only one read/write repo per handler']; end
9
+ class InjectingRepoOutsideAggregate < Realm::Error['Cannot auto inject repository outside of an aggregate']; end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ protected
17
+
18
+ def use_repo(*names, readonly: self < Realm::QueryHandler)
19
+ raise OnlyOneWriteRepo if !readonly && (names.size > 1 || defined?(@write_repo_injected))
20
+
21
+ names << default_repo_name if names.empty?
22
+ names.each { |name| inject_repo(name, readonly) }
23
+ @write_repo_injected = true unless readonly
24
+ end
25
+
26
+ private
27
+
28
+ def inject_repo(name, readonly)
29
+ repo_name = "#{name}_repo"
30
+ return inject(repo_name) unless readonly
31
+
32
+ inject(repo_name) { |repo| repo.respond_to?(:readonly) ? repo.readonly : repo }
33
+ end
34
+
35
+ def default_repo_name
36
+ raise InjectingRepoOutsideAggregate unless respond_to?(:aggregate)
37
+
38
+ aggregate
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end