igniter 0.2.0 → 0.3.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +224 -1
  4. data/docs/API_V2.md +296 -1
  5. data/docs/BACKLOG.md +166 -0
  6. data/docs/BRANCHES_V1.md +213 -0
  7. data/docs/COLLECTIONS_V1.md +303 -0
  8. data/docs/EXECUTION_MODEL_V2.md +79 -0
  9. data/docs/PATTERNS.md +222 -0
  10. data/docs/STORE_ADAPTERS.md +126 -0
  11. data/examples/README.md +127 -0
  12. data/examples/async_store.rb +47 -0
  13. data/examples/collection.rb +43 -0
  14. data/examples/collection_partial_failure.rb +50 -0
  15. data/examples/marketing_ergonomics.rb +57 -0
  16. data/examples/ringcentral_routing.rb +269 -0
  17. data/lib/igniter/compiler/compiled_graph.rb +90 -0
  18. data/lib/igniter/compiler/graph_compiler.rb +12 -2
  19. data/lib/igniter/compiler/type_resolver.rb +54 -0
  20. data/lib/igniter/compiler/validation_context.rb +61 -0
  21. data/lib/igniter/compiler/validation_pipeline.rb +30 -0
  22. data/lib/igniter/compiler/validator.rb +1 -187
  23. data/lib/igniter/compiler/validators/callable_validator.rb +107 -0
  24. data/lib/igniter/compiler/validators/dependencies_validator.rb +153 -0
  25. data/lib/igniter/compiler/validators/outputs_validator.rb +66 -0
  26. data/lib/igniter/compiler/validators/type_compatibility_validator.rb +84 -0
  27. data/lib/igniter/compiler/validators/uniqueness_validator.rb +60 -0
  28. data/lib/igniter/compiler.rb +8 -0
  29. data/lib/igniter/contract.rb +152 -4
  30. data/lib/igniter/diagnostics/auditing/report/console_formatter.rb +80 -0
  31. data/lib/igniter/diagnostics/auditing/report/markdown_formatter.rb +22 -0
  32. data/lib/igniter/diagnostics/introspection/formatters/mermaid_formatter.rb +58 -0
  33. data/lib/igniter/diagnostics/introspection/formatters/text_tree_formatter.rb +44 -0
  34. data/lib/igniter/diagnostics/report.rb +186 -11
  35. data/lib/igniter/dsl/contract_builder.rb +271 -5
  36. data/lib/igniter/dsl/schema_builder.rb +73 -0
  37. data/lib/igniter/dsl.rb +1 -0
  38. data/lib/igniter/errors.rb +11 -0
  39. data/lib/igniter/events/bus.rb +5 -0
  40. data/lib/igniter/events/event.rb +29 -0
  41. data/lib/igniter/executor.rb +74 -0
  42. data/lib/igniter/executor_registry.rb +44 -0
  43. data/lib/igniter/extensions/auditing/timeline.rb +4 -0
  44. data/lib/igniter/extensions/introspection/graph_formatter.rb +33 -3
  45. data/lib/igniter/extensions/introspection/plan_formatter.rb +55 -0
  46. data/lib/igniter/extensions/introspection/runtime_formatter.rb +18 -3
  47. data/lib/igniter/extensions/introspection.rb +1 -0
  48. data/lib/igniter/extensions/reactive/engine.rb +49 -2
  49. data/lib/igniter/extensions/reactive/reaction.rb +3 -2
  50. data/lib/igniter/model/branch_node.rb +46 -0
  51. data/lib/igniter/model/collection_node.rb +31 -0
  52. data/lib/igniter/model/composition_node.rb +2 -2
  53. data/lib/igniter/model/compute_node.rb +58 -2
  54. data/lib/igniter/model/input_node.rb +2 -2
  55. data/lib/igniter/model/output_node.rb +24 -4
  56. data/lib/igniter/model.rb +2 -0
  57. data/lib/igniter/runtime/cache.rb +64 -25
  58. data/lib/igniter/runtime/collection_result.rb +111 -0
  59. data/lib/igniter/runtime/deferred_result.rb +40 -0
  60. data/lib/igniter/runtime/execution.rb +261 -11
  61. data/lib/igniter/runtime/input_validator.rb +2 -24
  62. data/lib/igniter/runtime/invalidator.rb +1 -1
  63. data/lib/igniter/runtime/job_worker.rb +18 -0
  64. data/lib/igniter/runtime/node_state.rb +20 -0
  65. data/lib/igniter/runtime/planner.rb +126 -0
  66. data/lib/igniter/runtime/resolver.rb +310 -15
  67. data/lib/igniter/runtime/result.rb +14 -2
  68. data/lib/igniter/runtime/runner_factory.rb +20 -0
  69. data/lib/igniter/runtime/runners/inline_runner.rb +21 -0
  70. data/lib/igniter/runtime/runners/store_runner.rb +29 -0
  71. data/lib/igniter/runtime/runners/thread_pool_runner.rb +37 -0
  72. data/lib/igniter/runtime/stores/active_record_store.rb +41 -0
  73. data/lib/igniter/runtime/stores/file_store.rb +43 -0
  74. data/lib/igniter/runtime/stores/memory_store.rb +40 -0
  75. data/lib/igniter/runtime/stores/redis_store.rb +44 -0
  76. data/lib/igniter/runtime.rb +12 -0
  77. data/lib/igniter/type_system.rb +44 -0
  78. data/lib/igniter/version.rb +1 -1
  79. data/lib/igniter.rb +23 -0
  80. metadata +43 -2
data/lib/igniter/dsl.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "dsl/contract_builder"
4
+ require_relative "dsl/schema_builder"
4
5
 
5
6
  module Igniter
6
7
  module DSL
@@ -48,6 +48,17 @@ module Igniter
48
48
  class ValidationError < CompileError; end
49
49
  class CycleError < ValidationError; end
50
50
  class InputError < Error; end
51
+ class CollectionInputError < Error; end
52
+ class CollectionKeyError < Error; end
51
53
  class ResolutionError < Error; end
52
54
  class CompositionError < Error; end
55
+ class BranchSelectionError < Error; end
56
+ class PendingDependencyError < Error
57
+ attr_reader :deferred_result
58
+
59
+ def initialize(deferred_result, message = "Dependency is pending", context: {})
60
+ @deferred_result = deferred_result
61
+ super(message, context: context)
62
+ end
63
+ end
53
64
  end
@@ -34,6 +34,11 @@ module Igniter
34
34
  def subscribe(subscriber = nil, &block)
35
35
  @subscribers << (subscriber || block)
36
36
  end
37
+
38
+ def restore!(events:, execution_id: nil)
39
+ @execution_id = execution_id if execution_id
40
+ @events = Array(events).map { |event| event.is_a?(Event) ? event : Event.from_h(event) }
41
+ end
37
42
  end
38
43
  end
39
44
  end
@@ -16,6 +16,20 @@ module Igniter
16
16
  :timestamp,
17
17
  keyword_init: true
18
18
  ) do
19
+ def self.from_h(data)
20
+ new(
21
+ event_id: value_from(data, :event_id),
22
+ type: value_from(data, :type).to_sym,
23
+ execution_id: value_from(data, :execution_id),
24
+ node_id: value_from(data, :node_id),
25
+ node_name: value_from(data, :node_name)&.to_sym,
26
+ path: value_from(data, :path),
27
+ status: value_from(data, :status)&.to_sym,
28
+ payload: value_from(data, :payload) || {},
29
+ timestamp: parse_timestamp(value_from(data, :timestamp))
30
+ )
31
+ end
32
+
19
33
  def to_h
20
34
  {
21
35
  event_id: event_id,
@@ -36,6 +50,21 @@ module Igniter
36
50
 
37
51
  private
38
52
 
53
+ def self.parse_timestamp(value)
54
+ case value
55
+ when Time
56
+ value
57
+ when String
58
+ Time.iso8601(value)
59
+ else
60
+ value
61
+ end
62
+ end
63
+
64
+ def self.value_from(data, key)
65
+ data[key] || data[key.to_s]
66
+ end
67
+
39
68
  def serialize_value(value)
40
69
  case value
41
70
  when Time
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ class Executor
5
+ class << self
6
+ def inherited(subclass)
7
+ super
8
+ subclass.instance_variable_set(:@executor_inputs, executor_inputs.transform_values(&:dup))
9
+ subclass.instance_variable_set(:@executor_metadata, executor_metadata.dup)
10
+ end
11
+
12
+ def input(name, required: true, type: nil, **metadata)
13
+ executor_inputs[name.to_sym] = metadata.merge(required: required, type: type).compact
14
+ end
15
+
16
+ def executor_inputs
17
+ @executor_inputs ||= {}
18
+ end
19
+
20
+ def executor_metadata
21
+ @executor_metadata ||= {}
22
+ end
23
+
24
+ def executor_key(value = nil)
25
+ metadata_value(:key, value)
26
+ end
27
+
28
+ def label(value = nil)
29
+ metadata_value(:label, value)
30
+ end
31
+
32
+ def category(value = nil)
33
+ metadata_value(:category, value)
34
+ end
35
+
36
+ def summary(value = nil)
37
+ metadata_value(:summary, value)
38
+ end
39
+
40
+ def tags(*values)
41
+ return Array(executor_metadata[:tags]).freeze if values.empty?
42
+
43
+ executor_metadata[:tags] = values.flatten.compact.map(&:to_sym).freeze
44
+ end
45
+
46
+ def output_schema(value = nil)
47
+ metadata_value(:output_schema, value)
48
+ end
49
+
50
+ def call(**dependencies)
51
+ new.call(**dependencies)
52
+ end
53
+
54
+ private
55
+
56
+ def metadata_value(key, value)
57
+ return executor_metadata[key] if value.nil?
58
+
59
+ executor_metadata[key] = value
60
+ end
61
+ end
62
+
63
+ attr_reader :execution, :contract
64
+
65
+ def initialize(execution: nil, contract: nil)
66
+ @execution = execution
67
+ @contract = contract
68
+ end
69
+
70
+ def defer(token: nil, payload: {})
71
+ Runtime::DeferredResult.build(token: token, payload: payload)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ class ExecutorRegistry
5
+ Definition = Struct.new(:key, :executor_class, :metadata, keyword_init: true)
6
+
7
+ def initialize
8
+ @definitions = {}
9
+ end
10
+
11
+ def register(key, executor_class, **metadata)
12
+ normalized_key = key.to_s
13
+ raise CompileError, "executor registry key cannot be empty" if normalized_key.empty?
14
+
15
+ unless executor_class.is_a?(Class) && executor_class <= Igniter::Executor
16
+ raise CompileError, "Executor registry key '#{normalized_key}' must reference an Igniter::Executor subclass"
17
+ end
18
+
19
+ @definitions[normalized_key] = Definition.new(
20
+ key: normalized_key,
21
+ executor_class: executor_class,
22
+ metadata: executor_class.executor_metadata.merge(metadata).merge(key: normalized_key)
23
+ )
24
+ end
25
+
26
+ def fetch(key)
27
+ @definitions.fetch(key.to_s)
28
+ rescue KeyError
29
+ raise CompileError, "Unknown executor registry key: #{key}"
30
+ end
31
+
32
+ def registered?(key)
33
+ @definitions.key?(key.to_s)
34
+ end
35
+
36
+ def definitions
37
+ @definitions.values.sort_by(&:key)
38
+ end
39
+
40
+ def clear
41
+ @definitions.clear
42
+ end
43
+ end
44
+ end
@@ -19,6 +19,10 @@ module Igniter
19
19
  @events.dup
20
20
  end
21
21
 
22
+ def restore!(events)
23
+ @events = Array(events).map { |event| event.is_a?(Igniter::Events::Event) ? event : Igniter::Events::Event.from_h(event) }
24
+ end
25
+
22
26
  def snapshot
23
27
  {
24
28
  execution_id: execution.events.execution_id,
@@ -23,9 +23,35 @@ module Igniter
23
23
  @graph.nodes.each do |node|
24
24
  line = "- #{node.kind} #{node.path}"
25
25
  line += " depends_on=#{node.dependencies.join(',')}" if node.dependencies.any?
26
+ if node.kind == :compute
27
+ line += " callable=#{node.callable_name}"
28
+ line += " guard=true" if node.guard?
29
+ line += " const=true" if node.const?
30
+ line += " executor_key=#{node.executor_key}" if node.executor_key
31
+ line += " label=#{node.executor_label}" if node.executor_label
32
+ line += " category=#{node.executor_category}" if node.executor_category
33
+ line += " tags=#{node.executor_tags.join(',')}" if node.executor_tags.any?
34
+ line += " summary=#{node.executor_summary}" if node.executor_summary
35
+ end
26
36
  if node.kind == :composition
27
37
  line += " contract=#{node.contract_class.name || 'AnonymousContract'}"
28
38
  end
39
+ if node.kind == :branch
40
+ cases = node.cases.map { |entry| "#{entry[:match].inspect}:#{entry[:contract].name || 'AnonymousContract'}" }
41
+ line += " selector=#{node.selector_dependency}"
42
+ line += " depends_on=#{node.context_dependencies.join(',')}" if node.context_dependencies.any?
43
+ line += " cases=#{cases.join('|')}"
44
+ line += " default=#{node.default_contract.name || 'AnonymousContract'}"
45
+ line += " mapper=#{node.input_mapper}" if node.input_mapper?
46
+ end
47
+ if node.kind == :collection
48
+ line += " with=#{node.source_dependency}"
49
+ line += " depends_on=#{node.context_dependencies.join(',')}" if node.context_dependencies.any?
50
+ line += " each=#{node.contract_class.name || 'AnonymousContract'}"
51
+ line += " key=#{node.key_name}"
52
+ line += " mode=#{node.mode}"
53
+ line += " mapper=#{node.input_mapper}" if node.input_mapper?
54
+ end
29
55
  lines << line
30
56
  end
31
57
  lines << "Outputs:"
@@ -43,11 +69,11 @@ module Igniter
43
69
  end
44
70
  @graph.outputs.each do |output|
45
71
  lines << %( #{output_id(output)}["output: #{output.name}"])
46
- lines << %( #{node_id(@graph.fetch_node(output.source))} --> #{output_id(output)})
72
+ lines << %( #{node_id(@graph.fetch_node(output.source_root))} --> #{output_id(output)})
47
73
  end
48
74
  @graph.nodes.each do |node|
49
75
  node.dependencies.each do |dependency_name|
50
- dependency_node = @graph.fetch_node(dependency_name)
76
+ dependency_node = @graph.fetch_dependency(dependency_name)
51
77
  lines << %( #{node_id(dependency_node)} --> #{node_id(node)})
52
78
  end
53
79
  end
@@ -57,6 +83,8 @@ module Igniter
57
83
  private
58
84
 
59
85
  def node_id(node)
86
+ return "output_#{node.name}" if node.kind == :output
87
+
60
88
  "node_#{node.name}"
61
89
  end
62
90
 
@@ -65,7 +93,9 @@ module Igniter
65
93
  end
66
94
 
67
95
  def node_label(node)
68
- "#{node.kind}: #{node.name}"
96
+ return "#{node.kind}: #{node.path}" unless node.kind == :compute
97
+
98
+ "#{node.kind}: #{node.path}\\n#{node.callable_name}"
69
99
  end
70
100
  end
71
101
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Introspection
6
+ class PlanFormatter
7
+ def self.to_text(execution, output_names = nil)
8
+ new(execution, output_names).to_text
9
+ end
10
+
11
+ def initialize(execution, output_names = nil)
12
+ @execution = execution
13
+ @plan = execution.plan(output_names)
14
+ end
15
+
16
+ def to_text
17
+ lines = []
18
+ lines << "Plan #{@execution.compiled_graph.name}"
19
+ lines << "Targets: #{format_list(@plan[:targets])}"
20
+ lines << "Ready: #{format_list(@plan[:ready])}"
21
+ lines << "Blocked: #{format_list(@plan[:blocked])}"
22
+ lines << "Nodes:"
23
+
24
+ @plan[:nodes].each_value do |entry|
25
+ lines << format_node(entry)
26
+ end
27
+
28
+ lines.join("\n")
29
+ end
30
+
31
+ private
32
+
33
+ def format_node(entry)
34
+ line = "- #{entry[:kind]} #{entry[:path]} status=#{entry[:status]}"
35
+ line += " ready=true" if entry[:ready]
36
+ line += " blocked=true" if entry[:blocked]
37
+ line += " waiting_on=#{format_list(entry[:waiting_on])}" if entry[:waiting_on].any?
38
+
39
+ dependency_summary = entry[:dependencies].map do |dependency|
40
+ "#{dependency[:name]}(#{dependency[:status]})"
41
+ end
42
+ line += " deps=#{dependency_summary.join(',')}" if dependency_summary.any?
43
+ line
44
+ end
45
+
46
+ def format_list(values)
47
+ array = Array(values)
48
+ return "none" if array.empty?
49
+
50
+ array.join(",")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -24,9 +24,9 @@ module Igniter
24
24
 
25
25
  def explain_output(output_name)
26
26
  output = @execution.compiled_graph.fetch_output(output_name)
27
- source = @execution.compiled_graph.fetch_node(output.source)
27
+ source = @execution.compiled_graph.fetch_node(output.source_root)
28
28
 
29
- {
29
+ explanation = {
30
30
  output_id: output.id,
31
31
  output: output.name,
32
32
  path: output.path,
@@ -35,6 +35,13 @@ module Igniter
35
35
  source_path: source.path,
36
36
  dependencies: dependency_tree(source)
37
37
  }
38
+
39
+ if output.composition_output?
40
+ explanation[:child_output] = output.child_output_name
41
+ explanation[:child_output_path] = output.source.to_s
42
+ end
43
+
44
+ explanation
38
45
  end
39
46
 
40
47
  private
@@ -52,7 +59,7 @@ module Igniter
52
59
  value: serialize_value(state&.value),
53
60
  error: state&.error&.message,
54
61
  dependencies: node.dependencies.map do |dependency_name|
55
- dependency_tree(@execution.compiled_graph.fetch_node(dependency_name))
62
+ dependency_tree(@execution.compiled_graph.fetch_dependency(dependency_name))
56
63
  end
57
64
  }
58
65
  end
@@ -84,6 +91,14 @@ module Igniter
84
91
 
85
92
  def serialize_value(value)
86
93
  case value
94
+ when Igniter::Runtime::DeferredResult
95
+ {
96
+ type: :deferred,
97
+ token: value.token,
98
+ payload: value.payload,
99
+ source_node: value.source_node,
100
+ waiting_on: value.waiting_on
101
+ }
87
102
  when Igniter::Runtime::Result
88
103
  {
89
104
  type: :result,
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "introspection/graph_formatter"
4
+ require_relative "introspection/plan_formatter"
4
5
  require_relative "introspection/runtime_formatter"
5
6
 
6
7
  module Igniter
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  module Igniter
4
6
  module Extensions
5
7
  module Reactive
@@ -11,16 +13,21 @@ module Igniter
11
13
  @contract = contract
12
14
  @reactions = reactions
13
15
  @errors = []
16
+ @fired_reactions = Set.new
14
17
  end
15
18
 
16
19
  def call(event)
17
20
  reactions.each do |reaction|
18
21
  next unless Matcher.new(reaction, event).match?
22
+ next if already_fired?(reaction, event)
19
23
 
20
- reaction.action.call(
24
+ mark_fired(reaction, event)
25
+ call_action(
26
+ reaction.action,
21
27
  event: event,
22
28
  contract: contract,
23
- execution: execution
29
+ execution: execution,
30
+ value: value_for(event)
24
31
  )
25
32
  rescue StandardError => e
26
33
  @errors << {
@@ -30,6 +37,46 @@ module Igniter
30
37
  }
31
38
  end
32
39
  end
40
+
41
+ private
42
+
43
+ def call_action(action, **kwargs)
44
+ parameters = action.parameters
45
+ accepts_any_keywords = parameters.any? { |kind, _name| kind == :keyrest }
46
+
47
+ if accepts_any_keywords
48
+ action.call(**kwargs)
49
+ return
50
+ end
51
+
52
+ accepted_keywords = parameters.select { |kind, _name| %i[key keyreq].include?(kind) }.map(&:last)
53
+ filtered_kwargs = kwargs.slice(*accepted_keywords)
54
+ action.call(**filtered_kwargs)
55
+ end
56
+
57
+ def value_for(event)
58
+ return nil unless %i[node_succeeded node_resumed node_pending].include?(event.type)
59
+ return nil unless event.node_name
60
+
61
+ state = execution.cache.fetch(event.node_name)
62
+ state&.value
63
+ end
64
+
65
+ def already_fired?(reaction, event)
66
+ return false unless reaction.once_per_execution
67
+
68
+ @fired_reactions.include?(reaction_key(reaction, event))
69
+ end
70
+
71
+ def mark_fired(reaction, event)
72
+ return unless reaction.once_per_execution
73
+
74
+ @fired_reactions << reaction_key(reaction, event)
75
+ end
76
+
77
+ def reaction_key(reaction, event)
78
+ [reaction.object_id, event.execution_id]
79
+ end
33
80
  end
34
81
  end
35
82
  end
@@ -4,12 +4,13 @@ module Igniter
4
4
  module Extensions
5
5
  module Reactive
6
6
  class Reaction
7
- attr_reader :event_type, :path, :action
7
+ attr_reader :event_type, :path, :action, :once_per_execution
8
8
 
9
- def initialize(event_type:, path: nil, action:)
9
+ def initialize(event_type:, path: nil, action:, once_per_execution: false)
10
10
  @event_type = event_type.to_sym
11
11
  @path = path&.to_s
12
12
  @action = action
13
+ @once_per_execution = once_per_execution
13
14
  end
14
15
  end
15
16
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Model
5
+ class BranchNode < Node
6
+ attr_reader :selector_dependency, :cases, :default_contract, :input_mapping, :context_dependencies, :input_mapper
7
+
8
+ def initialize(id:, name:, selector_dependency:, cases:, default_contract:, input_mapping:, context_dependencies: [], input_mapper: nil, path: nil, metadata: {})
9
+ dependencies = ([selector_dependency] + input_mapping.values + context_dependencies).uniq
10
+
11
+ super(
12
+ id: id,
13
+ kind: :branch,
14
+ name: name,
15
+ path: (path || name),
16
+ dependencies: dependencies,
17
+ metadata: metadata
18
+ )
19
+
20
+ @selector_dependency = selector_dependency.to_sym
21
+ @cases = cases.map { |entry| normalize_case(entry) }.freeze
22
+ @default_contract = default_contract
23
+ @input_mapping = input_mapping.transform_keys(&:to_sym).transform_values(&:to_sym).freeze
24
+ @context_dependencies = Array(context_dependencies).map(&:to_sym).freeze
25
+ @input_mapper = input_mapper
26
+ end
27
+
28
+ def possible_contracts
29
+ (cases.map { |entry| entry[:contract] } + [default_contract]).uniq
30
+ end
31
+
32
+ def input_mapper?
33
+ !input_mapper.nil?
34
+ end
35
+
36
+ private
37
+
38
+ def normalize_case(entry)
39
+ {
40
+ match: entry.fetch(:match),
41
+ contract: entry.fetch(:contract)
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Model
5
+ class CollectionNode < Node
6
+ attr_reader :source_dependency, :contract_class, :key_name, :mode, :context_dependencies, :input_mapper
7
+
8
+ def initialize(id:, name:, source_dependency:, contract_class:, key_name:, mode:, context_dependencies: [], input_mapper: nil, path: nil, metadata: {})
9
+ super(
10
+ id: id,
11
+ kind: :collection,
12
+ name: name,
13
+ path: (path || name),
14
+ dependencies: [source_dependency, *context_dependencies],
15
+ metadata: metadata
16
+ )
17
+
18
+ @source_dependency = source_dependency.to_sym
19
+ @contract_class = contract_class
20
+ @key_name = key_name.to_sym
21
+ @mode = mode.to_sym
22
+ @context_dependencies = Array(context_dependencies).map(&:to_sym)
23
+ @input_mapper = input_mapper
24
+ end
25
+
26
+ def input_mapper?
27
+ !input_mapper.nil?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,12 +5,12 @@ module Igniter
5
5
  class CompositionNode < Node
6
6
  attr_reader :contract_class, :input_mapping
7
7
 
8
- def initialize(id:, name:, contract_class:, input_mapping:, metadata: {})
8
+ def initialize(id:, name:, contract_class:, input_mapping:, path: nil, metadata: {})
9
9
  super(
10
10
  id: id,
11
11
  kind: :composition,
12
12
  name: name,
13
- path: name,
13
+ path: (path || name),
14
14
  dependencies: input_mapping.values,
15
15
  metadata: metadata
16
16
  )
@@ -5,17 +5,73 @@ module Igniter
5
5
  class ComputeNode < Node
6
6
  attr_reader :callable
7
7
 
8
- def initialize(id:, name:, dependencies:, callable:, metadata: {})
8
+ def initialize(id:, name:, dependencies:, callable:, path: nil, metadata: {})
9
9
  super(
10
10
  id: id,
11
11
  kind: :compute,
12
12
  name: name,
13
- path: name,
13
+ path: (path || name),
14
14
  dependencies: dependencies,
15
15
  metadata: metadata
16
16
  )
17
17
  @callable = callable
18
18
  end
19
+
20
+ def callable_name
21
+ return "const" if const?
22
+ return "guard" if guard?
23
+
24
+ case callable
25
+ when Proc
26
+ "proc"
27
+ when Symbol, String
28
+ callable.to_s
29
+ when Class
30
+ callable.name || "AnonymousClass"
31
+ else
32
+ callable.class.name || "AnonymousCallable"
33
+ end
34
+ end
35
+
36
+ def executor_key
37
+ metadata[:executor_key] || executor_metadata[:key]
38
+ end
39
+
40
+ def executor_label
41
+ metadata[:label] || executor_metadata[:label]
42
+ end
43
+
44
+ def executor_category
45
+ metadata[:category] || executor_metadata[:category]
46
+ end
47
+
48
+ def executor_tags
49
+ Array(metadata[:tags] || executor_metadata[:tags]).freeze
50
+ end
51
+
52
+ def executor_summary
53
+ metadata[:summary] || executor_metadata[:summary]
54
+ end
55
+
56
+ def type
57
+ metadata[:type] || executor_metadata[:type]
58
+ end
59
+
60
+ def const?
61
+ metadata[:kind] == :const
62
+ end
63
+
64
+ def guard?
65
+ metadata[:guard] == true || metadata[:kind] == :guard
66
+ end
67
+
68
+ private
69
+
70
+ def executor_metadata
71
+ return {} unless callable.is_a?(Class) && callable <= Igniter::Executor
72
+
73
+ callable.executor_metadata
74
+ end
19
75
  end
20
76
  end
21
77
  end
@@ -3,8 +3,8 @@
3
3
  module Igniter
4
4
  module Model
5
5
  class InputNode < Node
6
- def initialize(id:, name:, metadata: {})
7
- super(id: id, kind: :input, name: name, path: name, metadata: metadata)
6
+ def initialize(id:, name:, path: nil, metadata: {})
7
+ super(id: id, kind: :input, name: name, path: (path || name), metadata: metadata)
8
8
  end
9
9
 
10
10
  def type