Module: Hecks::Runtime::ReactionInvocation
- Defined in:
- lib/hecks/runtime/reaction_invocation.rb
Overview
Turns facts selected by a policy or process manager into the same
receiver/payload envelope an outside caller uses. Reaction declarations
historically selected both through one with: map, so this is the one
compatibility seam that separates receiver identities from facts after
resolving the declaration and before re-entering the dispatcher.
Defined Under Namespace
Class Method Summary collapse
-
.build(registry:, verb:, projected:, explicit:, passthrough: [], source_receiver: nil) ⇒ Object
A reaction without an explicit projection retains wholesale legacy forwarding.
-
.projection_declared?(declaration) ⇒ Boolean
The holding IR historically represented both an omitted projection and an explicitly empty
with: {}as the same empty array. -
.resolve_mapping(with_spec:, scopes:, bindings: {}, label: "reaction") ⇒ Object
A reaction's source names resolve lexically, not globally.
Class Method Details
.build(registry:, verb:, projected:, explicit:, passthrough: [], source_receiver: nil) ⇒ Object
A reaction without an explicit projection retains wholesale legacy
forwarding. Choosing with: opts into the strict envelope: only target
command attributes enter with:, while identities become to:.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/hecks/runtime/reaction_invocation.rb', line 68 def build(registry:, verb:, projected:, explicit:, passthrough: [], source_receiver: nil) args = projected.transform_keys(&:to_sym) unless explicit return args unless source_receiver # Compatibility calls still belong to Dispatcher when the target is # absent: it owns the canonical UnknownVerb wording. Resolution here # is only an optional opportunity to lift same-aggregate Event.id. begin target = resolve_target(registry, verb) rescue UnknownVerb return args end inherited_receiver = source_receiver_for(target, source_receiver) return inherited_receiver ? args.merge(to: inherited_receiver) : args end target = resolve_target(registry, verb) inherited_receiver = source_receiver_for(target, source_receiver) facts = command_facts(target.command, args) consumed = facts.keys if target.entities.empty? && target.command.creates? refuse_unconsumed!(target.command, args, consumed, passthrough) return { with: facts } end aggregate_identity, aggregate_keys = identity_for( target.aggregate, args, aliases: aggregate_aliases(target), value_owner: target.aggregate ) aggregate_identity ||= inherited_receiver require_identity!(verb, target.aggregate, aggregate_identity) consumed.concat(aggregate_keys) entity_identities = target.entities.map do |entity| identity, keys = identity_for( entity, args, aliases: [Naming.reference_key(entity.hecks_name)], value_owner: target.aggregate ) require_identity!(verb, entity, identity) consumed.concat(keys) identity end refuse_unconsumed!(target.command, args, consumed, passthrough) route = if entity_identities.empty? aggregate_identity else { aggregate: aggregate_identity, entities: entity_identities } end { to: route, with: facts } end |
.projection_declared?(declaration) ⇒ Boolean
The holding IR historically represented both an omitted projection and
an explicitly empty with: {} as the same empty array. Builders now
preserve declaration presence off-wire; reconstructed/legacy IR falls
back to the old non-empty reading.
26 27 28 29 30 31 32 |
# File 'lib/hecks/runtime/reaction_invocation.rb', line 26 def projection_declared?(declaration) if declaration.instance_variable_defined?(:@projection_declared) declaration.instance_variable_get(:@projection_declared) else !declaration.with_spec.to_a.empty? end end |
.resolve_mapping(with_spec:, scopes:, bindings: {}, label: "reaction") ⇒ Object
A reaction's source names resolve lexically, not globally. Policies supply one event/row scope. Process managers supply current event then opening-event memory, while correlation is an explicit binding ahead of both. Missing names are refused here rather than materialized as nil and accidentally presented as target command facts.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/hecks/runtime/reaction_invocation.rb', line 39 def resolve_mapping(with_spec:, scopes:, bindings: {}, label: "reaction") normalized_bindings = bindings.transform_keys(&:to_sym) normalized_scopes = scopes.map do |scope| scope = Scope.new(name: scope.first, facts: scope.last) unless scope.is_a?(Scope) Scope.new(name: scope.name.to_s, facts: scope.facts.transform_keys(&:to_sym)) end with_spec.to_h do |key, source| value = if !source.is_a?(Symbol) source elsif normalized_bindings.key?(source) normalized_bindings.fetch(source) else visible = normalized_scopes.find { |scope| scope.facts.key?(source) } unless visible names = normalized_scopes.map(&:name).join(" then ") raise UnknownArgument, "#{label}'s with: reads :#{source}, which is not visible in #{names}" end visible.facts.fetch(source) end [key.to_sym, Value.materialize(value)] end end |