igniter 0.2.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +264 -0
- data/docs/API_V2.md +242 -0
- data/docs/ARCHITECTURE_V2.md +317 -0
- data/docs/EXECUTION_MODEL_V2.md +245 -0
- data/docs/IGNITER_CONCEPTS.md +81 -0
- data/examples/README.md +77 -0
- data/examples/basic_pricing.rb +27 -0
- data/examples/composition.rb +39 -0
- data/examples/diagnostics.rb +28 -0
- data/lib/igniter/compiler/compiled_graph.rb +78 -0
- data/lib/igniter/compiler/graph_compiler.rb +60 -0
- data/lib/igniter/compiler/validator.rb +205 -0
- data/lib/igniter/compiler.rb +10 -0
- data/lib/igniter/contract.rb +117 -0
- data/lib/igniter/diagnostics/report.rb +174 -0
- data/lib/igniter/diagnostics.rb +8 -0
- data/lib/igniter/dsl/contract_builder.rb +95 -0
- data/lib/igniter/dsl.rb +8 -0
- data/lib/igniter/errors.rb +53 -0
- data/lib/igniter/events/bus.rb +39 -0
- data/lib/igniter/events/event.rb +53 -0
- data/lib/igniter/events.rb +9 -0
- data/lib/igniter/extensions/auditing/timeline.rb +99 -0
- data/lib/igniter/extensions/auditing.rb +10 -0
- data/lib/igniter/extensions/introspection/graph_formatter.rb +73 -0
- data/lib/igniter/extensions/introspection/runtime_formatter.rb +102 -0
- data/lib/igniter/extensions/introspection.rb +11 -0
- data/lib/igniter/extensions/reactive/engine.rb +36 -0
- data/lib/igniter/extensions/reactive/matcher.rb +21 -0
- data/lib/igniter/extensions/reactive/reaction.rb +17 -0
- data/lib/igniter/extensions/reactive.rb +12 -0
- data/lib/igniter/extensions.rb +10 -0
- data/lib/igniter/model/composition_node.rb +22 -0
- data/lib/igniter/model/compute_node.rb +21 -0
- data/lib/igniter/model/graph.rb +15 -0
- data/lib/igniter/model/input_node.rb +27 -0
- data/lib/igniter/model/node.rb +22 -0
- data/lib/igniter/model/output_node.rb +21 -0
- data/lib/igniter/model.rb +13 -0
- data/lib/igniter/runtime/cache.rb +58 -0
- data/lib/igniter/runtime/execution.rb +142 -0
- data/lib/igniter/runtime/input_validator.rb +145 -0
- data/lib/igniter/runtime/invalidator.rb +52 -0
- data/lib/igniter/runtime/node_state.rb +31 -0
- data/lib/igniter/runtime/resolver.rb +114 -0
- data/lib/igniter/runtime/result.rb +105 -0
- data/lib/igniter/runtime.rb +14 -0
- data/lib/igniter/version.rb +5 -0
- data/lib/igniter.rb +20 -0
- data/sig/igniter.rbs +4 -0
- metadata +126 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
4
|
+
require "igniter"
|
|
5
|
+
|
|
6
|
+
class PriceContract < Igniter::Contract
|
|
7
|
+
define do
|
|
8
|
+
input :order_total, type: :numeric
|
|
9
|
+
input :country, type: :string
|
|
10
|
+
|
|
11
|
+
compute :vat_rate, depends_on: [:country] do |country:|
|
|
12
|
+
country == "UA" ? 0.2 : 0.0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
compute :gross_total, depends_on: %i[order_total vat_rate] do |order_total:, vat_rate:|
|
|
16
|
+
order_total * (1 + vat_rate)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
output :gross_total
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
contract = PriceContract.new(order_total: 100, country: "UA")
|
|
24
|
+
|
|
25
|
+
puts "gross_total=#{contract.result.gross_total}"
|
|
26
|
+
contract.update_inputs(order_total: 150)
|
|
27
|
+
puts "updated_gross_total=#{contract.result.gross_total}"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
4
|
+
require "igniter"
|
|
5
|
+
|
|
6
|
+
class PriceContract < Igniter::Contract
|
|
7
|
+
define do
|
|
8
|
+
input :order_total, type: :numeric
|
|
9
|
+
input :country, type: :string
|
|
10
|
+
|
|
11
|
+
compute :vat_rate, depends_on: [:country] do |country:|
|
|
12
|
+
country == "UA" ? 0.2 : 0.0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
compute :gross_total, depends_on: %i[order_total vat_rate] do |order_total:, vat_rate:|
|
|
16
|
+
order_total * (1 + vat_rate)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
output :gross_total
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class CheckoutContract < Igniter::Contract
|
|
24
|
+
define do
|
|
25
|
+
input :order_total, type: :numeric
|
|
26
|
+
input :country, type: :string
|
|
27
|
+
|
|
28
|
+
compose :pricing, contract: PriceContract, inputs: {
|
|
29
|
+
order_total: :order_total,
|
|
30
|
+
country: :country
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
output :pricing
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
contract = CheckoutContract.new(order_total: 100, country: "UA")
|
|
38
|
+
|
|
39
|
+
puts "pricing=#{contract.result.to_h.inspect}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
4
|
+
require "igniter"
|
|
5
|
+
|
|
6
|
+
class PriceContract < Igniter::Contract
|
|
7
|
+
define do
|
|
8
|
+
input :order_total, type: :numeric
|
|
9
|
+
input :country, type: :string
|
|
10
|
+
|
|
11
|
+
compute :vat_rate, depends_on: [:country] do |country:|
|
|
12
|
+
country == "UA" ? 0.2 : 0.0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
compute :gross_total, depends_on: %i[order_total vat_rate] do |order_total:, vat_rate:|
|
|
16
|
+
order_total * (1 + vat_rate)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
output :gross_total
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
contract = PriceContract.new(order_total: 100, country: "UA")
|
|
24
|
+
contract.result.gross_total
|
|
25
|
+
|
|
26
|
+
puts contract.diagnostics_text
|
|
27
|
+
puts "---"
|
|
28
|
+
puts contract.result.as_json.inspect
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Compiler
|
|
5
|
+
class CompiledGraph
|
|
6
|
+
attr_reader :name, :nodes, :nodes_by_id, :nodes_by_name, :nodes_by_path, :outputs, :outputs_by_name, :resolution_order, :dependents
|
|
7
|
+
|
|
8
|
+
def initialize(name:, nodes:, outputs:, resolution_order:, dependents:)
|
|
9
|
+
@name = name
|
|
10
|
+
@nodes = nodes.freeze
|
|
11
|
+
@nodes_by_id = nodes.each_with_object({}) { |node, memo| memo[node.id] = node }.freeze
|
|
12
|
+
@nodes_by_name = nodes.each_with_object({}) { |node, memo| memo[node.name] = node }.freeze
|
|
13
|
+
@nodes_by_path = nodes.each_with_object({}) { |node, memo| memo[node.path] = node }.freeze
|
|
14
|
+
@outputs = outputs.freeze
|
|
15
|
+
@outputs_by_name = outputs.each_with_object({}) { |node, memo| memo[node.name] = node }.freeze
|
|
16
|
+
@resolution_order = resolution_order.freeze
|
|
17
|
+
@dependents = dependents.transform_values(&:freeze).freeze
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def fetch_node_by_id(id)
|
|
22
|
+
@nodes_by_id.fetch(id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_node(name)
|
|
26
|
+
@nodes_by_name.fetch(name.to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def node?(name)
|
|
30
|
+
@nodes_by_name.key?(name.to_sym)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fetch_node_by_path(path)
|
|
34
|
+
@nodes_by_path.fetch(path.to_s)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fetch_output(name)
|
|
38
|
+
@outputs_by_name.fetch(name.to_sym)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_h
|
|
42
|
+
{
|
|
43
|
+
name: name,
|
|
44
|
+
nodes: nodes.map do |node|
|
|
45
|
+
base = {
|
|
46
|
+
id: node.id,
|
|
47
|
+
kind: node.kind,
|
|
48
|
+
name: node.name,
|
|
49
|
+
path: node.path,
|
|
50
|
+
dependencies: node.dependencies
|
|
51
|
+
}
|
|
52
|
+
if node.kind == :composition
|
|
53
|
+
base[:contract] = node.contract_class.name
|
|
54
|
+
base[:inputs] = node.input_mapping
|
|
55
|
+
end
|
|
56
|
+
base
|
|
57
|
+
end,
|
|
58
|
+
outputs: outputs.map do |output|
|
|
59
|
+
{
|
|
60
|
+
name: output.name,
|
|
61
|
+
path: output.path,
|
|
62
|
+
source: output.source
|
|
63
|
+
}
|
|
64
|
+
end,
|
|
65
|
+
resolution_order: resolution_order.map(&:name)
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_text
|
|
70
|
+
Extensions::Introspection::GraphFormatter.to_text(self)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_mermaid
|
|
74
|
+
Extensions::Introspection::GraphFormatter.to_mermaid(self)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tsort"
|
|
4
|
+
|
|
5
|
+
module Igniter
|
|
6
|
+
module Compiler
|
|
7
|
+
class GraphCompiler
|
|
8
|
+
include TSort
|
|
9
|
+
|
|
10
|
+
def self.call(graph)
|
|
11
|
+
new(graph).call
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(graph)
|
|
15
|
+
@graph = graph
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
validator = Validator.call(@graph)
|
|
20
|
+
@nodes_by_name = validator.runtime_nodes_by_name
|
|
21
|
+
|
|
22
|
+
CompiledGraph.new(
|
|
23
|
+
name: @graph.name,
|
|
24
|
+
nodes: validator.runtime_nodes,
|
|
25
|
+
outputs: validator.outputs,
|
|
26
|
+
resolution_order: tsort,
|
|
27
|
+
dependents: build_dependents
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def runtime_nodes
|
|
34
|
+
@runtime_nodes ||= @graph.nodes.reject { |node| node.kind == :output }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_dependents
|
|
38
|
+
runtime_nodes.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |node, memo|
|
|
39
|
+
node.dependencies.each { |dependency_name| memo[dependency_name] << node.name }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def tsort_each_node(&block)
|
|
44
|
+
runtime_nodes.each(&block)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def tsort_each_child(node, &block)
|
|
48
|
+
node.dependencies.each do |dependency_name|
|
|
49
|
+
block.call(@nodes_by_name.fetch(dependency_name))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tsort
|
|
54
|
+
super
|
|
55
|
+
rescue TSort::Cyclic => e
|
|
56
|
+
raise CycleError, e.message
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Compiler
|
|
5
|
+
class Validator
|
|
6
|
+
def self.call(graph)
|
|
7
|
+
new(graph).call
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(graph)
|
|
11
|
+
@graph = graph
|
|
12
|
+
@runtime_nodes_by_name = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
validate_unique_ids!
|
|
17
|
+
index_runtime_nodes!
|
|
18
|
+
validate_outputs!
|
|
19
|
+
validate_unique_paths!
|
|
20
|
+
validate_dependencies!
|
|
21
|
+
validate_callable_signatures!
|
|
22
|
+
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def runtime_nodes
|
|
27
|
+
@runtime_nodes ||= @graph.nodes.reject { |node| node.kind == :output }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def outputs
|
|
31
|
+
@outputs ||= @graph.nodes.select { |node| node.kind == :output }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def runtime_nodes_by_name
|
|
35
|
+
@runtime_nodes_by_name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def validate_unique_ids!
|
|
41
|
+
seen = {}
|
|
42
|
+
|
|
43
|
+
@graph.nodes.each do |node|
|
|
44
|
+
if seen.key?(node.id)
|
|
45
|
+
raise validation_error(node, "Duplicate node id: #{node.id}")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
seen[node.id] = true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def validate_unique_paths!
|
|
53
|
+
seen = {}
|
|
54
|
+
|
|
55
|
+
runtime_nodes.each do |node|
|
|
56
|
+
if seen.key?(node.path)
|
|
57
|
+
raise validation_error(node, "Duplicate node path: #{node.path}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
seen[node.path] = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
outputs.each do |output|
|
|
64
|
+
if seen.key?(output.path)
|
|
65
|
+
raise validation_error(output, "Duplicate node path: #{output.path}")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
seen[output.path] = true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def index_runtime_nodes!
|
|
73
|
+
runtime_nodes.each do |node|
|
|
74
|
+
raise validation_error(node, "Duplicate node name: #{node.name}") if @runtime_nodes_by_name.key?(node.name)
|
|
75
|
+
|
|
76
|
+
@runtime_nodes_by_name[node.name] = node
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def validate_outputs!
|
|
81
|
+
raise ValidationError, "Graph must define at least one output" if outputs.empty?
|
|
82
|
+
|
|
83
|
+
seen = {}
|
|
84
|
+
outputs.each do |output|
|
|
85
|
+
raise validation_error(output, "Duplicate output name: #{output.name}") if seen.key?(output.name)
|
|
86
|
+
raise validation_error(output, "Unknown output source '#{output.source}' for output '#{output.name}'") unless @runtime_nodes_by_name.key?(output.source)
|
|
87
|
+
|
|
88
|
+
seen[output.name] = true
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_dependencies!
|
|
93
|
+
runtime_nodes.each do |node|
|
|
94
|
+
validate_composition_node!(node) if node.kind == :composition
|
|
95
|
+
|
|
96
|
+
node.dependencies.each do |dependency_name|
|
|
97
|
+
next if @runtime_nodes_by_name.key?(dependency_name)
|
|
98
|
+
|
|
99
|
+
raise validation_error(node, "Unknown dependency '#{dependency_name}' for node '#{node.name}'")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def validate_composition_node!(node)
|
|
105
|
+
contract_class = node.contract_class
|
|
106
|
+
|
|
107
|
+
unless contract_class.is_a?(Class) && contract_class <= Igniter::Contract
|
|
108
|
+
raise validation_error(node, "Composition '#{node.name}' must reference an Igniter::Contract subclass")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
unless contract_class.compiled_graph
|
|
112
|
+
raise validation_error(node, "Composition '#{node.name}' references an uncompiled contract")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
validate_composition_input_mapping!(node, contract_class.compiled_graph)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def validate_composition_input_mapping!(node, child_graph)
|
|
119
|
+
child_input_nodes = child_graph.nodes.select { |child_node| child_node.kind == :input }
|
|
120
|
+
child_input_names = child_input_nodes.map(&:name)
|
|
121
|
+
|
|
122
|
+
unknown_inputs = node.input_mapping.keys - child_input_names
|
|
123
|
+
unless unknown_inputs.empty?
|
|
124
|
+
raise validation_error(
|
|
125
|
+
node,
|
|
126
|
+
"Composition '#{node.name}' maps unknown child inputs: #{unknown_inputs.sort.join(', ')}"
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
missing_required_inputs = child_input_nodes
|
|
131
|
+
.select(&:required?)
|
|
132
|
+
.reject { |child_input| node.input_mapping.key?(child_input.name) }
|
|
133
|
+
.map(&:name)
|
|
134
|
+
|
|
135
|
+
return if missing_required_inputs.empty?
|
|
136
|
+
|
|
137
|
+
raise validation_error(
|
|
138
|
+
node,
|
|
139
|
+
"Composition '#{node.name}' is missing mappings for required child inputs: #{missing_required_inputs.sort.join(', ')}"
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def validate_callable_signatures!
|
|
144
|
+
runtime_nodes.each do |node|
|
|
145
|
+
next unless node.kind == :compute
|
|
146
|
+
next unless node.callable.is_a?(Proc)
|
|
147
|
+
|
|
148
|
+
validate_proc_signature!(node)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def validate_proc_signature!(node)
|
|
153
|
+
parameters = node.callable.parameters
|
|
154
|
+
positional_kinds = %i[req opt rest]
|
|
155
|
+
positional = parameters.select { |kind, _name| positional_kinds.include?(kind) }
|
|
156
|
+
|
|
157
|
+
unless positional.empty?
|
|
158
|
+
raise validation_error(
|
|
159
|
+
node,
|
|
160
|
+
"Compute '#{node.name}' proc must use keyword arguments for dependencies, got positional parameters"
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
accepts_any_keywords = parameters.any? { |kind, _name| kind == :keyrest }
|
|
165
|
+
accepted_keywords = parameters
|
|
166
|
+
.select { |kind, _name| %i[key keyreq].include?(kind) }
|
|
167
|
+
.map(&:last)
|
|
168
|
+
required_keywords = parameters
|
|
169
|
+
.select { |kind, _name| kind == :keyreq }
|
|
170
|
+
.map(&:last)
|
|
171
|
+
|
|
172
|
+
missing_dependencies = required_keywords - node.dependencies
|
|
173
|
+
unless missing_dependencies.empty?
|
|
174
|
+
raise validation_error(
|
|
175
|
+
node,
|
|
176
|
+
"Compute '#{node.name}' requires undeclared dependencies: #{missing_dependencies.sort.join(', ')}"
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
return if accepts_any_keywords
|
|
181
|
+
|
|
182
|
+
unknown_dependencies = node.dependencies - accepted_keywords
|
|
183
|
+
return if unknown_dependencies.empty?
|
|
184
|
+
|
|
185
|
+
raise validation_error(
|
|
186
|
+
node,
|
|
187
|
+
"Compute '#{node.name}' declares unsupported dependencies for its proc: #{unknown_dependencies.sort.join(', ')}"
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def validation_error(node, message)
|
|
192
|
+
ValidationError.new(
|
|
193
|
+
message,
|
|
194
|
+
context: {
|
|
195
|
+
graph: @graph.name,
|
|
196
|
+
node_id: node.id,
|
|
197
|
+
node_name: node.name,
|
|
198
|
+
node_path: node.path,
|
|
199
|
+
source_location: node.source_location
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
class Contract
|
|
5
|
+
class << self
|
|
6
|
+
def define(&block)
|
|
7
|
+
@compiled_graph = DSL::ContractBuilder.compile(name: contract_name, &block)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def react_to(event_type, path: nil, &block)
|
|
11
|
+
raise CompileError, "react_to requires a block" unless block
|
|
12
|
+
|
|
13
|
+
reactions << Extensions::Reactive::Reaction.new(
|
|
14
|
+
event_type: event_type,
|
|
15
|
+
path: path,
|
|
16
|
+
action: block
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def compiled_graph
|
|
21
|
+
@compiled_graph || superclass_compiled_graph
|
|
22
|
+
end
|
|
23
|
+
alias graph compiled_graph
|
|
24
|
+
|
|
25
|
+
def reactions
|
|
26
|
+
@reactions ||= []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def contract_name
|
|
32
|
+
name || "AnonymousContract"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def superclass_compiled_graph
|
|
36
|
+
return unless superclass.respond_to?(:compiled_graph)
|
|
37
|
+
|
|
38
|
+
superclass.compiled_graph
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attr_reader :execution, :result
|
|
43
|
+
|
|
44
|
+
def initialize(inputs = {})
|
|
45
|
+
graph = self.class.compiled_graph
|
|
46
|
+
raise CompileError, "#{self.class.name} has no compiled graph. Use `define`." unless graph
|
|
47
|
+
|
|
48
|
+
@execution = Runtime::Execution.new(
|
|
49
|
+
compiled_graph: graph,
|
|
50
|
+
contract_instance: self,
|
|
51
|
+
inputs: inputs
|
|
52
|
+
)
|
|
53
|
+
@reactive = Extensions::Reactive::Engine.new(
|
|
54
|
+
execution: @execution,
|
|
55
|
+
contract: self,
|
|
56
|
+
reactions: self.class.reactions
|
|
57
|
+
)
|
|
58
|
+
@execution.events.subscribe(@reactive)
|
|
59
|
+
@result = Runtime::Result.new(@execution)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def resolve
|
|
63
|
+
execution.resolve_all
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resolve_all
|
|
68
|
+
resolve
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def update_inputs(inputs)
|
|
72
|
+
execution.update_inputs(inputs)
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def events
|
|
77
|
+
execution.events.events
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def audit
|
|
81
|
+
execution.audit
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def audit_snapshot
|
|
85
|
+
execution.audit.snapshot
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def reactive
|
|
89
|
+
@reactive
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def subscribe(subscriber = nil, &block)
|
|
93
|
+
execution.events.subscribe(subscriber, &block)
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def diagnostics
|
|
98
|
+
Diagnostics::Report.new(execution)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def diagnostics_text
|
|
102
|
+
diagnostics.to_text
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def diagnostics_markdown
|
|
106
|
+
diagnostics.to_markdown
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def success?
|
|
110
|
+
execution.success?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def failed?
|
|
114
|
+
execution.failed?
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|