servactory 3.0.0.rc1 → 3.0.0.rc3

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.
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Exceptions
6
- # Raised when accessing registry before finalization
7
- #
8
- # ## Purpose
9
- #
10
- # Indicates that the Stroma::Registry was accessed before finalize! was called.
11
- # The registry must be finalized before it can be used to ensure all DSL modules
12
- # are registered in the correct order.
13
- #
14
- # ## Usage
15
- #
16
- # Raised when accessing registry methods before finalization:
17
- #
18
- # ```ruby
19
- # # Before finalize! is called
20
- # Servactory::Stroma::Registry.entries
21
- # # Raises: Servactory::Stroma::Exceptions::RegistryNotFinalized
22
- # ```
23
- #
24
- # ## Integration
25
- #
26
- # This exception typically indicates that Servactory::DSL module was not
27
- # properly loaded. Ensure servactory gem is properly required before
28
- # defining service classes.
29
- class RegistryNotFinalized < Base
30
- end
31
- end
32
- end
33
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Exceptions
6
- # Raised when using an invalid hook target key
7
- #
8
- # ## Purpose
9
- #
10
- # Indicates that an unknown key was used as a hook target in the
11
- # extensions block. Only registered DSL module keys can be used
12
- # as hook targets.
13
- #
14
- # ## Usage
15
- #
16
- # Raised when using an invalid key in extensions block:
17
- #
18
- # ```ruby
19
- # class ApplicationService::Base
20
- # include Servactory::DSL
21
- #
22
- # extensions do
23
- # before :unknown_key, SomeModule
24
- # # Raises: Servactory::Stroma::Exceptions::UnknownHookTarget
25
- # end
26
- # end
27
- # ```
28
- #
29
- # ## Integration
30
- #
31
- # Valid hook target keys are determined by registered DSL modules:
32
- # :configuration, :info, :context, :inputs, :internals, :outputs, :actions
33
- #
34
- # Check Servactory::Stroma::Registry.keys for the list of valid targets.
35
- class UnknownHookTarget < Base
36
- end
37
- end
38
- end
39
- end
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Hooks
6
- # Applies registered hooks to a target class.
7
- #
8
- # ## Purpose
9
- #
10
- # Iterates through all registered DSL modules and includes corresponding
11
- # before/after hooks in the target class. For each registry entry,
12
- # before hooks are included first, then after hooks.
13
- #
14
- # ## Usage
15
- #
16
- # ```ruby
17
- # applier = Servactory::Stroma::Hooks::Applier.new(ChildService, hooks)
18
- # applier.apply!
19
- # # ChildService now includes all hook modules
20
- # ```
21
- #
22
- # ## Integration
23
- #
24
- # Called by Servactory::Stroma::DSL.inherited after duplicating
25
- # parent's configuration. Uses Registry.entries to determine
26
- # hook application order.
27
- class Applier
28
- # Creates a new applier for applying hooks to a class.
29
- #
30
- # @param target_class [Class] The class to apply hooks to
31
- # @param hooks [Collection] The hooks collection to apply
32
- def initialize(target_class, hooks)
33
- @target_class = target_class
34
- @hooks = hooks
35
- end
36
-
37
- # Applies all registered hooks to the target class.
38
- #
39
- # For each registry entry, includes before hooks first,
40
- # then after hooks. Does nothing if hooks collection is empty.
41
- #
42
- # @return [void]
43
- #
44
- # @example
45
- # applier.apply!
46
- # # Target class now includes all extension modules
47
- def apply!
48
- return if @hooks.empty?
49
-
50
- Registry.entries.each do |entry|
51
- @hooks.before(entry.key).each do |hook|
52
- @target_class.include(hook.extension)
53
- end
54
-
55
- @hooks.after(entry.key).each do |hook|
56
- @target_class.include(hook.extension)
57
- end
58
- end
59
- end
60
- end
61
- end
62
- end
63
- end
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Hooks
6
- # Mutable collection manager for Hook objects.
7
- #
8
- # ## Purpose
9
- #
10
- # Stores Hook objects and provides query methods to retrieve hooks
11
- # by type and target key. Supports proper duplication during class
12
- # inheritance to ensure configuration isolation.
13
- #
14
- # ## Usage
15
- #
16
- # ```ruby
17
- # hooks = Servactory::Stroma::Hooks::Collection.new
18
- # hooks.add(:before, :actions, MyModule)
19
- # hooks.add(:after, :actions, AnotherModule)
20
- #
21
- # hooks.before(:actions) # => [Hook(...)]
22
- # hooks.after(:actions) # => [Hook(...)]
23
- # hooks.empty? # => false
24
- # hooks.size # => 2
25
- # ```
26
- #
27
- # ## Integration
28
- #
29
- # Stored in Servactory::Stroma::State and used by
30
- # Servactory::Stroma::Hooks::Applier to apply hooks to classes.
31
- # Properly duplicated during class inheritance via initialize_dup.
32
- class Collection
33
- extend Forwardable
34
-
35
- # @!method each
36
- # Iterates over all hooks in the collection.
37
- # @yield [Hook] Each hook in the collection
38
- # @!method map
39
- # Maps over all hooks in the collection.
40
- # @yield [Hook] Each hook in the collection
41
- # @return [Array] Mapped results
42
- # @!method size
43
- # Returns the number of hooks in the collection.
44
- # @return [Integer] Number of hooks
45
- # @!method empty?
46
- # Checks if the collection is empty.
47
- # @return [Boolean] true if no hooks registered
48
- def_delegators :@collection, :each, :map, :size, :empty?
49
-
50
- # Creates a new hooks collection.
51
- #
52
- # @param collection [Set] Initial collection of hooks (default: empty Set)
53
- def initialize(collection = Set.new)
54
- @collection = collection
55
- end
56
-
57
- # Creates a deep copy during inheritance.
58
- #
59
- # @param original [Collection] The original collection being duplicated
60
- # @return [void]
61
- def initialize_dup(original)
62
- super
63
- @collection = original.instance_variable_get(:@collection).dup
64
- end
65
-
66
- # Adds a new hook to the collection.
67
- #
68
- # @param type [Symbol] Hook type (:before or :after)
69
- # @param target_key [Symbol] Registry key to hook into
70
- # @param extension [Module] Extension module to include
71
- # @return [Set] The updated collection
72
- #
73
- # @example
74
- # hooks.add(:before, :actions, ValidationModule)
75
- def add(type, target_key, extension)
76
- @collection << Hook.new(type:, target_key:, extension:)
77
- end
78
-
79
- # Returns all before hooks for a given key.
80
- #
81
- # @param key [Symbol] The target key to filter by
82
- # @return [Array<Hook>] Hooks that run before the target
83
- #
84
- # @example
85
- # hooks.before(:actions) # => [Hook(type: :before, ...)]
86
- def before(key)
87
- @collection.select { |hook| hook.before? && hook.target_key == key }
88
- end
89
-
90
- # Returns all after hooks for a given key.
91
- #
92
- # @param key [Symbol] The target key to filter by
93
- # @return [Array<Hook>] Hooks that run after the target
94
- #
95
- # @example
96
- # hooks.after(:actions) # => [Hook(type: :after, ...)]
97
- def after(key)
98
- @collection.select { |hook| hook.after? && hook.target_key == key }
99
- end
100
- end
101
- end
102
- end
103
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Hooks
6
- # DSL interface for registering hooks in extensions block.
7
- #
8
- # ## Purpose
9
- #
10
- # Provides the `before` and `after` methods used within the extensions
11
- # block to register hooks. Validates that target keys exist in Registry
12
- # before adding hooks.
13
- #
14
- # ## Usage
15
- #
16
- # ```ruby
17
- # class ApplicationService::Base < Servactory::Base
18
- # extensions do
19
- # before :actions, ValidationModule
20
- # after :outputs, LoggingModule
21
- # end
22
- # end
23
- # ```
24
- #
25
- # ## Integration
26
- #
27
- # Created by DSL.extensions method and receives instance_eval of the block.
28
- # Validates keys against Registry.keys and raises UnknownHookTarget
29
- # for invalid keys.
30
- class Factory
31
- # Creates a new factory for registering hooks.
32
- #
33
- # @param hooks [Collection] The hooks collection to add to
34
- def initialize(hooks)
35
- @hooks = hooks
36
- end
37
-
38
- # Registers one or more before hooks for a target key.
39
- #
40
- # @param key [Symbol] The registry key to hook before
41
- # @param extensions [Array<Module>] Extension modules to include
42
- # @raise [Exceptions::UnknownHookTarget] If key is not registered
43
- #
44
- # @example
45
- # before :actions, ValidationModule, AuthorizationModule
46
- def before(key, *extensions)
47
- validate_key!(key)
48
- extensions.each { |extension| @hooks.add(:before, key, extension) }
49
- end
50
-
51
- # Registers one or more after hooks for a target key.
52
- #
53
- # @param key [Symbol] The registry key to hook after
54
- # @param extensions [Array<Module>] Extension modules to include
55
- # @raise [Exceptions::UnknownHookTarget] If key is not registered
56
- #
57
- # @example
58
- # after :outputs, LoggingModule, AuditModule
59
- def after(key, *extensions)
60
- validate_key!(key)
61
- extensions.each { |extension| @hooks.add(:after, key, extension) }
62
- end
63
-
64
- private
65
-
66
- # Validates that the key exists in the Registry.
67
- #
68
- # @param key [Symbol] The key to validate
69
- # @raise [Exceptions::UnknownHookTarget] If key is not registered
70
- def validate_key!(key)
71
- return if Registry.key?(key)
72
-
73
- raise Exceptions::UnknownHookTarget,
74
- "Unknown hook target: #{key.inspect}. " \
75
- "Valid keys: #{Registry.keys.map(&:inspect).join(', ')}"
76
- end
77
- end
78
- end
79
- end
80
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Hooks
6
- # Valid hook types for Hook validation.
7
- VALID_HOOK_TYPES = %i[before after].freeze
8
- private_constant :VALID_HOOK_TYPES
9
-
10
- # Immutable value object representing a hook configuration.
11
- #
12
- # ## Purpose
13
- #
14
- # Defines when and where an extension module should be included
15
- # relative to a registered DSL module. Hook is immutable - once
16
- # created, it cannot be modified.
17
- #
18
- # ## Attributes
19
- #
20
- # @!attribute [r] type
21
- # @return [Symbol] Either :before or :after
22
- # @!attribute [r] target_key
23
- # @return [Symbol] Key of the DSL module to hook into (:inputs, :actions, etc.)
24
- # @!attribute [r] extension
25
- # @return [Module] The module to include at the hook point
26
- #
27
- # ## Usage
28
- #
29
- # ```ruby
30
- # hook = Servactory::Stroma::Hooks::Hook.new(
31
- # type: :before,
32
- # target_key: :actions,
33
- # extension: MyExtension
34
- # )
35
- # hook.before? # => true
36
- # hook.after? # => false
37
- # ```
38
- #
39
- # ## Immutability
40
- #
41
- # Hook is a Data object - frozen and immutable after creation.
42
- Hook = Data.define(:type, :target_key, :extension) do
43
- # Initializes a new Hook with validation.
44
- #
45
- # @param type [Symbol] Hook type (:before or :after)
46
- # @param target_key [Symbol] Registry key to hook into
47
- # @param extension [Module] Extension module to include
48
- # @raise [Exceptions::InvalidHookType] If type is invalid
49
- def initialize(type:, target_key:, extension:)
50
- if VALID_HOOK_TYPES.exclude?(type)
51
- raise Exceptions::InvalidHookType,
52
- "Invalid hook type: #{type.inspect}. Valid types: #{VALID_HOOK_TYPES.map(&:inspect).join(', ')}"
53
- end
54
-
55
- super
56
- end
57
-
58
- # Checks if this is a before hook.
59
- #
60
- # @return [Boolean] true if type is :before
61
- def before?
62
- type == :before
63
- end
64
-
65
- # Checks if this is an after hook.
66
- #
67
- # @return [Boolean] true if type is :after
68
- def after?
69
- type == :after
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- # Manages global registration of DSL modules for Servactory.
6
- #
7
- # ## Purpose
8
- #
9
- # Singleton registry that stores all DSL modules that will be included
10
- # in service classes. Implements two-phase lifecycle: registration
11
- # followed by finalization.
12
- #
13
- # ## Usage
14
- #
15
- # ```ruby
16
- # # During gem initialization:
17
- # Servactory::Stroma::Registry.register(:inputs, Servactory::Inputs::DSL)
18
- # Servactory::Stroma::Registry.register(:outputs, Servactory::Outputs::DSL)
19
- # Servactory::Stroma::Registry.finalize!
20
- #
21
- # # After finalization:
22
- # Servactory::Stroma::Registry.keys # => [:inputs, :outputs]
23
- # Servactory::Stroma::Registry.key?(:inputs) # => true
24
- # ```
25
- #
26
- # ## Integration
27
- #
28
- # Used by Servactory::Stroma::DSL to include all registered modules in service classes.
29
- # Used by Servactory::Stroma::Hooks::Factory to validate hook target keys.
30
- #
31
- # ## Thread Safety
32
- #
33
- # Registration must occur during single-threaded boot phase.
34
- # After finalization, all read operations are thread-safe.
35
- class Registry
36
- include Singleton
37
-
38
- class << self
39
- delegate :register,
40
- :finalize!,
41
- :entries,
42
- :keys,
43
- :key?,
44
- to: :instance
45
- end
46
-
47
- def initialize
48
- @entries = []
49
- @finalized = false
50
- end
51
-
52
- def register(key, extension)
53
- raise Exceptions::RegistryFrozen, "Registry is finalized" if @finalized
54
-
55
- if @entries.any? { |e| e.key == key }
56
- raise Exceptions::KeyAlreadyRegistered, "Key #{key.inspect} already registered"
57
- end
58
-
59
- @entries << Entry.new(key:, extension:)
60
- end
61
-
62
- def finalize!
63
- return if @finalized
64
-
65
- @entries.freeze
66
- @finalized = true
67
- end
68
-
69
- def entries
70
- ensure_finalized!
71
- @entries
72
- end
73
-
74
- def keys
75
- ensure_finalized!
76
- @entries.map(&:key)
77
- end
78
-
79
- def key?(key)
80
- ensure_finalized!
81
- @entries.any? { |e| e.key == key }
82
- end
83
-
84
- private
85
-
86
- def ensure_finalized!
87
- return if @finalized
88
-
89
- raise Exceptions::RegistryNotFinalized,
90
- "Registry not finalized. Call Stroma::Registry.finalize! after registration."
91
- end
92
- end
93
- end
94
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Settings
6
- # Top-level hierarchical container for extension settings.
7
- #
8
- # ## Purpose
9
- #
10
- # Provides two-level hierarchical access to extension settings:
11
- # registry_key -> extension_name -> setting values.
12
- # Auto-vivifies RegistrySettings on first access.
13
- #
14
- # ## Usage
15
- #
16
- # ```ruby
17
- # settings = Servactory::Stroma::Settings::Collection.new
18
- # settings[:actions][:authorization][:method_name] = :authorize
19
- # settings[:actions][:transactional][:enabled] = true
20
- #
21
- # settings.keys # => [:actions]
22
- # settings.empty? # => false
23
- # settings.to_h # => { actions: { authorization: { method_name: :authorize }, ... } }
24
- # ```
25
- #
26
- # ## Integration
27
- #
28
- # Stored in Servactory::Stroma::State alongside Hooks::Collection.
29
- # Accessed via `stroma.settings` in service classes.
30
- # Properly duplicated during class inheritance via initialize_dup.
31
- class Collection
32
- extend Forwardable
33
-
34
- # @!method each
35
- # Iterates over all registry key settings.
36
- # @yield [key, settings] Each registry key and its RegistrySettings
37
- # @!method keys
38
- # Returns all registry keys with settings.
39
- # @return [Array<Symbol>] List of registry keys
40
- # @!method size
41
- # Returns the number of registry keys configured.
42
- # @return [Integer] Number of registry keys
43
- # @!method empty?
44
- # Checks if no settings are configured.
45
- # @return [Boolean] true if empty
46
- # @!method map
47
- # Maps over all registry key settings.
48
- # @yield [key, settings] Each registry key and its RegistrySettings
49
- # @return [Array] Mapped results
50
- def_delegators :@storage, :each, :keys, :size, :empty?, :map
51
-
52
- # Creates a new settings collection.
53
- #
54
- # @param storage [Hash] Initial storage (default: empty Hash)
55
- def initialize(storage = {})
56
- @storage = storage
57
- end
58
-
59
- # Creates a deep copy during inheritance.
60
- #
61
- # @param original [Collection] The original collection being duplicated
62
- # @return [void]
63
- def initialize_dup(original)
64
- super
65
- @storage = original.instance_variable_get(:@storage).transform_values(&:dup)
66
- end
67
-
68
- # Accesses or creates RegistrySettings for a registry key.
69
- #
70
- # Auto-vivifies a new RegistrySettings on first access.
71
- #
72
- # @param registry_key [Symbol] The registry key (e.g., :actions)
73
- # @return [RegistrySettings] Settings for that registry key
74
- #
75
- # @example
76
- # settings[:actions][:authorization][:method_name] = :authorize
77
- def [](registry_key)
78
- @storage[registry_key.to_sym] ||= RegistrySettings.new
79
- end
80
-
81
- # Converts to a nested Hash.
82
- #
83
- # @return [Hash] Deep nested hash of all settings
84
- def to_h
85
- @storage.transform_values(&:to_h)
86
- end
87
- end
88
- end
89
- end
90
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Stroma
5
- module Settings
6
- # Collection of Setting objects for one registry key.
7
- #
8
- # ## Purpose
9
- #
10
- # Groups extension settings by registry key (e.g., :actions).
11
- # Provides auto-vivifying access to individual Setting objects.
12
- # This is the middle layer in the settings hierarchy.
13
- #
14
- # ## Usage
15
- #
16
- # ```ruby
17
- # settings = Servactory::Stroma::Settings::RegistrySettings.new
18
- # settings[:authorization][:method_name] = :authorize
19
- # settings[:transactional][:enabled] = true
20
- #
21
- # settings.keys # => [:authorization, :transactional]
22
- # settings.empty? # => false
23
- # ```
24
- #
25
- # ## Integration
26
- #
27
- # Used by Collection as second-level container.
28
- # Properly duplicated during class inheritance via initialize_dup.
29
- class RegistrySettings
30
- extend Forwardable
31
-
32
- # @!method each
33
- # Iterates over all extension settings.
34
- # @yield [name, setting] Each extension name and its Setting
35
- # @!method keys
36
- # Returns all extension names.
37
- # @return [Array<Symbol>] List of extension names
38
- # @!method size
39
- # Returns the number of extensions configured.
40
- # @return [Integer] Number of extensions
41
- # @!method empty?
42
- # Checks if no extensions are configured.
43
- # @return [Boolean] true if empty
44
- # @!method map
45
- # Maps over all extension settings.
46
- # @yield [name, setting] Each extension name and its Setting
47
- # @return [Array] Mapped results
48
- def_delegators :@storage, :each, :keys, :size, :empty?, :map
49
-
50
- # Creates a new registry settings container.
51
- #
52
- # @param storage [Hash] Initial storage (default: empty Hash)
53
- def initialize(storage = {})
54
- @storage = storage
55
- end
56
-
57
- # Creates a deep copy during inheritance.
58
- #
59
- # @param original [RegistrySettings] The original being duplicated
60
- # @return [void]
61
- def initialize_dup(original)
62
- super
63
- @storage = original.instance_variable_get(:@storage).transform_values(&:dup)
64
- end
65
-
66
- # Accesses or creates a Setting for an extension.
67
- #
68
- # Auto-vivifies a new Setting on first access.
69
- #
70
- # @param extension_name [Symbol] The extension name
71
- # @return [Setting] The extension's setting container
72
- #
73
- # @example
74
- # settings[:authorization][:method_name] = :authorize
75
- def [](extension_name)
76
- @storage[extension_name.to_sym] ||= Setting.new
77
- end
78
-
79
- # Converts to a nested Hash.
80
- #
81
- # @return [Hash] Nested hash of all settings
82
- def to_h
83
- @storage.transform_values(&:to_h)
84
- end
85
- end
86
- end
87
- end
88
- end