realm-core 0.7.3 → 0.7.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 (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