realm-core 0.7.3 → 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/realm-core.rb +11 -0
  3. data/lib/realm.rb +20 -0
  4. data/lib/realm/action_handler.rb +84 -0
  5. data/lib/realm/action_handler/result.rb +32 -0
  6. data/lib/realm/builder.rb +84 -0
  7. data/lib/realm/command_handler.rb +18 -0
  8. data/lib/realm/config.rb +56 -0
  9. data/lib/realm/container.rb +65 -0
  10. data/lib/realm/context.rb +35 -0
  11. data/lib/realm/dependency.rb +22 -0
  12. data/lib/realm/dispatcher.rb +66 -0
  13. data/lib/realm/domain_resolver.rb +53 -0
  14. data/lib/realm/error.rb +62 -0
  15. data/lib/realm/event.rb +55 -0
  16. data/lib/realm/event_factory.rb +49 -0
  17. data/lib/realm/event_handler.rb +94 -0
  18. data/lib/realm/event_router.rb +91 -0
  19. data/lib/realm/event_router/gateway.rb +50 -0
  20. data/lib/realm/event_router/internal_loop_gateway.rb +48 -0
  21. data/lib/realm/health_status.rb +44 -0
  22. data/lib/realm/mixins/aggregate_member.rb +25 -0
  23. data/lib/realm/mixins/context_injection.rb +47 -0
  24. data/lib/realm/mixins/controller.rb +50 -0
  25. data/lib/realm/mixins/decorator.rb +33 -0
  26. data/lib/realm/mixins/dependency_injection.rb +50 -0
  27. data/lib/realm/mixins/reactive.rb +30 -0
  28. data/lib/realm/mixins/repository_helper.rb +41 -0
  29. data/lib/realm/multi_worker.rb +30 -0
  30. data/lib/realm/persistence.rb +51 -0
  31. data/lib/realm/persistence/repository_query_handler_adapter.rb +21 -0
  32. data/lib/realm/plugin.rb +17 -0
  33. data/lib/realm/query_handler.rb +6 -0
  34. data/lib/realm/runtime.rb +51 -0
  35. data/lib/realm/runtime/session.rb +31 -0
  36. data/lib/realm/types.rb +9 -0
  37. metadata +36 -3
  38. data/README.md +0 -40
  39. data/Rakefile +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48d73715170e2bfb91f817fe7f09816980939bfd51d56e061a088908af40dd08
4
- data.tar.gz: bde68574affd81f8eaecb821921c6dba91fe151eaa9b1703cdf31fff14e934cf
3
+ metadata.gz: f8388b271b94435bba6f9cdc441ce02ce96d7bdd9814fc46c682e01e9809fd22
4
+ data.tar.gz: 140b5a6019cf16b4190d7c08280c2d674c708b1df8f299e26a572a2f77fba404
5
5
  SHA512:
6
- metadata.gz: ff5149ff935de4a9599679f6daf246af8c43a06b3fab9d35f941a5c7d84b05b6557b4ed13487b56f127afaabe7a4868a8900d918004214f0ab13ba359763db5f
7
- data.tar.gz: 681487eb34d5d79f090eb505966fda04406f917051b4bd5bd8fc0aa9df7d285b5dd7e5083ffc4bcd3f71d1f93b94c21fc1c8200f6c74aca1883baff2b80ac700
6
+ metadata.gz: 1fece35f80d41bd49dae18befac98c67b57a342c010d91fd6b5999daf433bd112cf65eaf22b352a924b82a2f87fbd129394fddde8c7700cdbe79f31aa5274e89
7
+ data.tar.gz: fde494ec4d17db0a0001a27db0ec2c2ebb428fef7e791aff597436b59298cef3a64f235bee9d33701e866ea9cae673e0ebaf2954c347ec33b622420a815eb8bf
data/lib/realm-core.rb ADDED
@@ -0,0 +1,11 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ require 'realm'
5
+ require 'zeitwerk'
6
+
7
+ loader = Zeitwerk::Loader.for_gem
8
+ loader.ignore(__FILE__)
9
+ loader.setup
10
+
11
+ # rubocop:enable Naming/FileName
data/lib/realm.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+
5
+ module Realm
6
+ class << self
7
+ # Setup realm in test/console
8
+ def setup(root_module, **options)
9
+ config = Realm::Config.new(root_module: root_module, **options)
10
+ Realm::Builder.setup(config)
11
+ end
12
+
13
+ # Bind realm in service/engine
14
+ def bind(root_module, **options)
15
+ setup(root_module, **options).tap do |builder|
16
+ root_module.define_singleton_method(:realm) { builder.runtime }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-validation'
4
+ require 'active_support/core_ext/module/delegation'
5
+ require 'realm/error'
6
+ require 'realm/mixins/context_injection'
7
+ require 'realm/mixins/repository_helper'
8
+ require 'realm/mixins/aggregate_member'
9
+ require_relative 'action_handler/result'
10
+
11
+ module Realm
12
+ class ActionHandler
13
+ extend Mixins::ContextInjection::ClassMethods
14
+ include Mixins::AggregateMember
15
+ include Mixins::RepositoryHelper
16
+
17
+ class << self
18
+ attr_reader :contracts
19
+
20
+ def call(action: :handle, params: {}, runtime: nil)
21
+ new(runtime: runtime).(action: action, params: params)
22
+ end
23
+
24
+ protected
25
+
26
+ def require_permission(*names)
27
+ # TODO: implement
28
+ end
29
+
30
+ def contract(&block)
31
+ @method_contract = Class.new(Dry::Validation::Contract, &block).new
32
+ end
33
+
34
+ def contract_schema(&block)
35
+ contract { schema(&block) }
36
+ end
37
+
38
+ def contract_params(&block)
39
+ contract { params(&block) }
40
+ end
41
+
42
+ def contract_json(&block)
43
+ contract { json(&block) }
44
+ end
45
+
46
+ def method_added(method_name)
47
+ super
48
+ return unless defined?(@method_contract)
49
+
50
+ @contracts ||= {}
51
+ @contracts[method_name] = @method_contract
52
+ remove_instance_variable(:@method_contract)
53
+ end
54
+ end
55
+
56
+ def initialize(runtime: nil)
57
+ @runtime = runtime
58
+ end
59
+
60
+ def call(action: :handle, params: {})
61
+ # TODO: check permissions
62
+ raise CannotHandleAction.new(self, action) unless respond_to?(action)
63
+
64
+ safe_params = validate(action, params.to_h)
65
+ send(action, **safe_params)
66
+ end
67
+
68
+ protected
69
+
70
+ delegate :context, to: :@runtime
71
+
72
+ private
73
+
74
+ def validate(action, params)
75
+ contract = self.class.contracts && self.class.contracts[action]
76
+ return params unless contract
77
+
78
+ result = contract.(params)
79
+ raise Realm::InvalidParams, result if result.failure?
80
+
81
+ result.to_h
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ class ActionHandler
5
+ # Tuple of label and value
6
+ class Result < Array
7
+ def self.[](first, second = nil)
8
+ return new(first, second).freeze if first.is_a?(Symbol) || first.is_a?(Realm::Event)
9
+
10
+ new(second || :ok, first).freeze
11
+ end
12
+
13
+ def label
14
+ self[0]
15
+ end
16
+
17
+ def event
18
+ label if label.is_a?(Realm::Event)
19
+ end
20
+
21
+ def value
22
+ self[1]
23
+ end
24
+
25
+ private
26
+
27
+ def initialize(label, value)
28
+ super([label, value])
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ class Builder
5
+ def self.setup(config)
6
+ new(config).setup
7
+ end
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ end
12
+
13
+ def setup
14
+ logger.info("Setting up #{cfg.root_module} realm")
15
+ register_domain_resolver
16
+ register_event_router
17
+ register_runtime
18
+ register_logger
19
+ register_dependencies
20
+ setup_plugins
21
+ config_persistence
22
+ self
23
+ end
24
+
25
+ def runtime
26
+ @container.resolve(Runtime)
27
+ end
28
+
29
+ private
30
+
31
+ def register_domain_resolver
32
+ container.register_factory(DomainResolver, constantize(cfg.domain_module))
33
+ end
34
+
35
+ def register_event_router
36
+ return if cfg.event_gateways.empty?
37
+
38
+ container.register_factory(EventRouter, cfg.event_gateways, prefix: cfg.prefix)
39
+ end
40
+
41
+ def register_runtime
42
+ container.register_factory(Runtime, container)
43
+ end
44
+
45
+ def register_logger
46
+ container.register(:logger, logger)
47
+ end
48
+
49
+ def register_dependencies
50
+ container.register_all(**cfg.dependencies)
51
+ end
52
+
53
+ def setup_plugins
54
+ Plugin.descendants.each do |plugin|
55
+ plugin.setup(cfg, container) if cfg.plugins.include?(plugin.plugin_name)
56
+ end
57
+ end
58
+
59
+ def config_persistence
60
+ return unless cfg.persistence_gateway.present?
61
+
62
+ Persistence.setup(container, cfg.persistence_gateway[:repositories])
63
+ end
64
+
65
+ def constantize(*parts)
66
+ return parts[0] unless parts[0].is_a?(String)
67
+
68
+ parts.join('::').safe_constantize
69
+ end
70
+
71
+ def cfg
72
+ @config
73
+ end
74
+
75
+ def container
76
+ @container ||= Container.new
77
+ end
78
+
79
+ def logger
80
+ @logger ||= cfg.logger || (defined?(Rails) && Rails.logger) ||
81
+ Logger.new($stdout, level: ENV.fetch('LOG_LEVEL', :info).to_sym)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ class CommandHandler < Realm::ActionHandler
5
+ include Mixins::Reactive
6
+
7
+ def call(*)
8
+ gateway = context[:rom]&.gateways&.dig(:default)
9
+ gateway ? gateway.transaction { super } : super
10
+ end
11
+
12
+ protected
13
+
14
+ def result(first, second = nil)
15
+ Result[first, second]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module Realm
6
+ class Config
7
+ extend Dry::Initializer
8
+
9
+ option :root_module
10
+ option :database_url, default: proc {}
11
+ option :prefix, default: proc {}
12
+ option :namespace, default: proc { root_module.to_s.underscore }
13
+ option :domain_module, default: proc { "#{root_module}::Domain" }
14
+ option :engine_class, default: proc { "#{root_module}::Engine" }
15
+ option :engine_path, default: proc { engine_class&.to_s&.safe_constantize&.root }
16
+ option :logger, default: proc {}
17
+ option :plugins, default: proc { [] }, reader: false
18
+ option :dependencies, default: proc { {} }
19
+ option :persistence_gateway, default: proc { database_url && { type: :rom, url: database_url } }, reader: false
20
+ option :event_gateway, default: proc {}, reader: false
21
+ option :event_gateways, default: proc {
22
+ @event_gateway ? { default: { **@event_gateway, default: true } } : {}
23
+ }
24
+
25
+ def plugins
26
+ Array(@plugins)
27
+ end
28
+
29
+ def persistence_gateway
30
+ return {} unless @persistence_gateway
31
+
32
+ class_path = engine_path && "#{engine_path}/app/persistence/#{namespace}"
33
+ repos_path = class_path && "#{class_path}/repositories"
34
+ repos_module = "#{root_module}::Repositories"
35
+ {
36
+ root_module: root_module,
37
+ class_path: class_path,
38
+ repos_path: repos_path,
39
+ repos_module: repos_module,
40
+ migration_path: engine_path && "#{engine_path}/db/migrate",
41
+ repositories: repositories(repos_path, repos_module),
42
+ }.merge(@persistence_gateway)
43
+ end
44
+
45
+ private
46
+
47
+ def repositories(repos_path, repos_module)
48
+ return [] unless repos_path
49
+
50
+ Dir[File.join(repos_path, '**', '*.rb')].each_with_object([]) do |filename, all|
51
+ matches = %r{^#{repos_path}/(.+)\.rb$}.match(filename)
52
+ all << "#{repos_module}::#{matches[1].camelize}".constantize if matches
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-container'
4
+
5
+ module Realm
6
+ class Container
7
+ include Dry::Container::Mixin
8
+ include Enumerable
9
+
10
+ def self.[](object)
11
+ object.is_a?(Container) ? object : Container.new(object)
12
+ end
13
+
14
+ def initialize(hash = {})
15
+ register_all(hash)
16
+ end
17
+
18
+ def register(key, contents = nil, options = {}, &block)
19
+ options[:klass] ||= contents.class if contents && !contents.is_a?(::Hash)
20
+ super(key, contents, options, &block)
21
+ end
22
+
23
+ def register_all(hash)
24
+ hash.each_pair do |key, value|
25
+ register(key, value)
26
+ end
27
+ end
28
+
29
+ def register_factory(klass, *args, as: nil, memoize: true, **kwargs) # rubocop:disable Naming/MethodParameterName
30
+ register(as || klass, klass: klass, memoize: memoize) do
31
+ create(klass, *args, **kwargs)
32
+ end
33
+ end
34
+
35
+ def create(klass, *args, **kwargs)
36
+ (klass.try(:dependencies) || []).each do |d|
37
+ fn = -> { resolve_dependable(sanitize_dependable(d.dependable), d.optional?) }
38
+ kwargs[d.name] = d.lazy? ? fn : fn.call
39
+ end
40
+ klass.new(*args, **kwargs)
41
+ end
42
+
43
+ def [](key)
44
+ resolve(key) if key?(key)
45
+ end
46
+
47
+ def resolve_all(klass)
48
+ _container.each_with_object([]) do |(_, item), all|
49
+ all << item.call if item.options[:klass] <= klass
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def sanitize_dependable(dependable)
56
+ dependable.is_a?(String) && dependable.match(/^[A-Z]/) ? dependable.constantize : dependable
57
+ end
58
+
59
+ def resolve_dependable(dependable, optional)
60
+ raise DependencyMissing, dependable unless optional || key?(dependable)
61
+
62
+ self[dependable]
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ class Context
5
+ include Enumerable
6
+
7
+ def initialize(*containers)
8
+ @containers = containers.map { |c| Container[c] }
9
+ end
10
+
11
+ def [](name)
12
+ @containers.each do |container|
13
+ return container[name] if container.key?(name)
14
+ end
15
+ nil
16
+ end
17
+
18
+ def key?(name)
19
+ @containers.any? { |container| container.key?(name) }
20
+ end
21
+
22
+ def merge(container_like)
23
+ container_like.blank? ? self : self.class.new(container_like, *@containers)
24
+ end
25
+
26
+ def each(&block)
27
+ @containers.each { |container| container.each(&block) }
28
+ end
29
+
30
+ # Just for testing
31
+ def override!(container)
32
+ @containers.prepend(container)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ class Dependency
5
+ attr_reader :dependable, :name
6
+
7
+ def initialize(dependable, as: nil, optional: false, lazy: false) # rubocop:disable Naming/MethodParameterName
8
+ @dependable = dependable
9
+ @name = as || dependable.to_s.demodulize.underscore.to_sym
10
+ @optional = optional
11
+ @lazy = lazy
12
+ end
13
+
14
+ def optional?
15
+ @optional
16
+ end
17
+
18
+ def lazy?
19
+ @lazy
20
+ end
21
+ end
22
+ end