igniter 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +224 -1
- data/docs/API_V2.md +238 -1
- data/docs/BACKLOG.md +166 -0
- data/docs/BRANCHES_V1.md +213 -0
- data/docs/COLLECTIONS_V1.md +303 -0
- data/docs/EXECUTION_MODEL_V2.md +79 -0
- data/docs/PATTERNS.md +222 -0
- data/docs/STORE_ADAPTERS.md +126 -0
- data/examples/README.md +124 -0
- data/examples/async_store.rb +47 -0
- data/examples/collection.rb +43 -0
- data/examples/collection_partial_failure.rb +50 -0
- data/examples/marketing_ergonomics.rb +57 -0
- data/examples/ringcentral_routing.rb +278 -0
- data/lib/igniter/compiler/compiled_graph.rb +82 -0
- data/lib/igniter/compiler/graph_compiler.rb +12 -2
- data/lib/igniter/compiler/type_resolver.rb +54 -0
- data/lib/igniter/compiler/validation_context.rb +61 -0
- data/lib/igniter/compiler/validation_pipeline.rb +30 -0
- data/lib/igniter/compiler/validator.rb +1 -187
- data/lib/igniter/compiler/validators/callable_validator.rb +107 -0
- data/lib/igniter/compiler/validators/dependencies_validator.rb +151 -0
- data/lib/igniter/compiler/validators/outputs_validator.rb +66 -0
- data/lib/igniter/compiler/validators/type_compatibility_validator.rb +84 -0
- data/lib/igniter/compiler/validators/uniqueness_validator.rb +60 -0
- data/lib/igniter/compiler.rb +8 -0
- data/lib/igniter/contract.rb +136 -4
- data/lib/igniter/diagnostics/auditing/report/console_formatter.rb +80 -0
- data/lib/igniter/diagnostics/auditing/report/markdown_formatter.rb +22 -0
- data/lib/igniter/diagnostics/introspection/formatters/mermaid_formatter.rb +58 -0
- data/lib/igniter/diagnostics/introspection/formatters/text_tree_formatter.rb +44 -0
- data/lib/igniter/diagnostics/report.rb +84 -8
- data/lib/igniter/dsl/contract_builder.rb +208 -5
- data/lib/igniter/dsl/schema_builder.rb +73 -0
- data/lib/igniter/dsl.rb +1 -0
- data/lib/igniter/errors.rb +11 -0
- data/lib/igniter/events/bus.rb +5 -0
- data/lib/igniter/events/event.rb +29 -0
- data/lib/igniter/executor.rb +74 -0
- data/lib/igniter/executor_registry.rb +44 -0
- data/lib/igniter/extensions/auditing/timeline.rb +4 -0
- data/lib/igniter/extensions/introspection/graph_formatter.rb +29 -3
- data/lib/igniter/extensions/introspection/plan_formatter.rb +55 -0
- data/lib/igniter/extensions/introspection/runtime_formatter.rb +18 -3
- data/lib/igniter/extensions/introspection.rb +1 -0
- data/lib/igniter/extensions/reactive/engine.rb +49 -2
- data/lib/igniter/extensions/reactive/reaction.rb +3 -2
- data/lib/igniter/model/branch_node.rb +40 -0
- data/lib/igniter/model/collection_node.rb +25 -0
- data/lib/igniter/model/composition_node.rb +2 -2
- data/lib/igniter/model/compute_node.rb +58 -2
- data/lib/igniter/model/input_node.rb +2 -2
- data/lib/igniter/model/output_node.rb +24 -4
- data/lib/igniter/model.rb +2 -0
- data/lib/igniter/runtime/cache.rb +64 -25
- data/lib/igniter/runtime/collection_result.rb +111 -0
- data/lib/igniter/runtime/deferred_result.rb +40 -0
- data/lib/igniter/runtime/execution.rb +261 -11
- data/lib/igniter/runtime/input_validator.rb +2 -24
- data/lib/igniter/runtime/invalidator.rb +1 -1
- data/lib/igniter/runtime/job_worker.rb +18 -0
- data/lib/igniter/runtime/node_state.rb +20 -0
- data/lib/igniter/runtime/planner.rb +126 -0
- data/lib/igniter/runtime/resolver.rb +269 -15
- data/lib/igniter/runtime/result.rb +14 -2
- data/lib/igniter/runtime/runner_factory.rb +20 -0
- data/lib/igniter/runtime/runners/inline_runner.rb +21 -0
- data/lib/igniter/runtime/runners/store_runner.rb +29 -0
- data/lib/igniter/runtime/runners/thread_pool_runner.rb +37 -0
- data/lib/igniter/runtime/stores/active_record_store.rb +41 -0
- data/lib/igniter/runtime/stores/file_store.rb +43 -0
- data/lib/igniter/runtime/stores/memory_store.rb +40 -0
- data/lib/igniter/runtime/stores/redis_store.rb +44 -0
- data/lib/igniter/runtime.rb +12 -0
- data/lib/igniter/type_system.rb +44 -0
- data/lib/igniter/version.rb +1 -1
- data/lib/igniter.rb +23 -0
- metadata +43 -2
|
@@ -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.
|
|
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.
|
|
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,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
|
|
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,40 @@
|
|
|
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
|
|
7
|
+
|
|
8
|
+
def initialize(id:, name:, selector_dependency:, cases:, default_contract:, input_mapping:, path: nil, metadata: {})
|
|
9
|
+
dependencies = ([selector_dependency] + input_mapping.values).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
|
+
end
|
|
25
|
+
|
|
26
|
+
def possible_contracts
|
|
27
|
+
(cases.map { |entry| entry[:contract] } + [default_contract]).uniq
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def normalize_case(entry)
|
|
33
|
+
{
|
|
34
|
+
match: entry.fetch(:match),
|
|
35
|
+
contract: entry.fetch(:contract)
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
|
7
|
+
|
|
8
|
+
def initialize(id:, name:, source_dependency:, contract_class:, key_name:, mode:, path: nil, metadata: {})
|
|
9
|
+
super(
|
|
10
|
+
id: id,
|
|
11
|
+
kind: :collection,
|
|
12
|
+
name: name,
|
|
13
|
+
path: (path || name),
|
|
14
|
+
dependencies: [source_dependency],
|
|
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
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
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
|
|
@@ -5,16 +5,36 @@ module Igniter
|
|
|
5
5
|
class OutputNode < Node
|
|
6
6
|
attr_reader :source
|
|
7
7
|
|
|
8
|
-
def initialize(id:, name:, source:, metadata: {})
|
|
8
|
+
def initialize(id:, name:, source:, path: nil, metadata: {})
|
|
9
|
+
normalized_source = source.to_s
|
|
10
|
+
|
|
9
11
|
super(
|
|
10
12
|
id: id,
|
|
11
13
|
kind: :output,
|
|
12
14
|
name: name,
|
|
13
|
-
path: "output.#{name}",
|
|
14
|
-
dependencies: [
|
|
15
|
+
path: (path || "output.#{name}"),
|
|
16
|
+
dependencies: [normalized_source.split(".").first],
|
|
15
17
|
metadata: metadata
|
|
16
18
|
)
|
|
17
|
-
@source =
|
|
19
|
+
@source = normalized_source.include?(".") ? normalized_source : normalized_source.to_sym
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def source_root
|
|
23
|
+
source.to_s.split(".").first.to_sym
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def composition_output?
|
|
27
|
+
source.to_s.include?(".")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def type
|
|
31
|
+
metadata[:type]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def child_output_name
|
|
35
|
+
return unless composition_output?
|
|
36
|
+
|
|
37
|
+
source.to_s.split(".", 2).last.to_sym
|
|
18
38
|
end
|
|
19
39
|
end
|
|
20
40
|
end
|
data/lib/igniter/model.rb
CHANGED
|
@@ -5,6 +5,8 @@ require_relative "model/graph"
|
|
|
5
5
|
require_relative "model/input_node"
|
|
6
6
|
require_relative "model/compute_node"
|
|
7
7
|
require_relative "model/composition_node"
|
|
8
|
+
require_relative "model/branch_node"
|
|
9
|
+
require_relative "model/collection_node"
|
|
8
10
|
require_relative "model/output_node"
|
|
9
11
|
|
|
10
12
|
module Igniter
|
|
@@ -5,47 +5,86 @@ module Igniter
|
|
|
5
5
|
class Cache
|
|
6
6
|
def initialize
|
|
7
7
|
@states = {}
|
|
8
|
+
@mutex = Mutex.new
|
|
9
|
+
@condition = ConditionVariable.new
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def fetch(node_name)
|
|
11
|
-
@states[node_name.to_sym]
|
|
13
|
+
@mutex.synchronize { @states[node_name.to_sym] }
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def write(state)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
node
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
@mutex.synchronize do
|
|
18
|
+
current = @states[state.node.name]
|
|
19
|
+
version = state.version || (current&.running? ? current.version : next_version(current))
|
|
20
|
+
@states[state.node.name] = NodeState.new(
|
|
21
|
+
node: state.node,
|
|
22
|
+
status: state.status,
|
|
23
|
+
value: state.value,
|
|
24
|
+
error: state.error,
|
|
25
|
+
version: version,
|
|
26
|
+
resolved_at: state.resolved_at,
|
|
27
|
+
invalidated_by: state.invalidated_by
|
|
28
|
+
)
|
|
29
|
+
@condition.broadcast
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def begin_resolution(node)
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
loop do
|
|
36
|
+
current = @states[node.name]
|
|
37
|
+
return [:cached, current] if current && !current.stale? && !current.running?
|
|
38
|
+
|
|
39
|
+
unless current&.running?
|
|
40
|
+
@states[node.name] = NodeState.new(
|
|
41
|
+
node: node,
|
|
42
|
+
status: :running,
|
|
43
|
+
value: current&.value,
|
|
44
|
+
error: current&.error,
|
|
45
|
+
version: next_version(current),
|
|
46
|
+
resolved_at: current&.resolved_at || Time.now.utc,
|
|
47
|
+
invalidated_by: nil
|
|
48
|
+
)
|
|
49
|
+
return [:started, @states[node.name]]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@condition.wait(@mutex)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
26
55
|
end
|
|
27
56
|
|
|
28
57
|
def stale!(node, invalidated_by:)
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
@mutex.synchronize do
|
|
59
|
+
current = @states[node.name]
|
|
60
|
+
return unless current
|
|
31
61
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
62
|
+
@states[node.name] = NodeState.new(
|
|
63
|
+
node: node,
|
|
64
|
+
status: :stale,
|
|
65
|
+
value: current.value,
|
|
66
|
+
error: current.error,
|
|
67
|
+
version: current.version + 1,
|
|
68
|
+
resolved_at: current.resolved_at,
|
|
69
|
+
invalidated_by: invalidated_by
|
|
70
|
+
)
|
|
71
|
+
@condition.broadcast
|
|
72
|
+
end
|
|
41
73
|
end
|
|
42
74
|
|
|
43
75
|
def values
|
|
44
|
-
@states.values
|
|
76
|
+
@mutex.synchronize { @states.values }
|
|
45
77
|
end
|
|
46
78
|
|
|
47
79
|
def to_h
|
|
48
|
-
@states.dup
|
|
80
|
+
@mutex.synchronize { @states.dup }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def restore!(states)
|
|
84
|
+
@mutex.synchronize do
|
|
85
|
+
@states = states.transform_keys(&:to_sym)
|
|
86
|
+
@condition.broadcast
|
|
87
|
+
end
|
|
49
88
|
end
|
|
50
89
|
|
|
51
90
|
private
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Runtime
|
|
5
|
+
class CollectionResult
|
|
6
|
+
Item = Struct.new(:key, :status, :result, :error, keyword_init: true) do
|
|
7
|
+
def succeeded?
|
|
8
|
+
status == :succeeded
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def failed?
|
|
12
|
+
status == :failed
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_h
|
|
16
|
+
{
|
|
17
|
+
key: key,
|
|
18
|
+
status: status,
|
|
19
|
+
result: serialize_result(result),
|
|
20
|
+
error: serialize_error(error)
|
|
21
|
+
}.compact
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def serialize_result(value)
|
|
27
|
+
case value
|
|
28
|
+
when Runtime::Result
|
|
29
|
+
value.to_h
|
|
30
|
+
else
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def serialize_error(value)
|
|
36
|
+
return nil unless value
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
type: value.class.name,
|
|
40
|
+
message: value.message,
|
|
41
|
+
context: value.respond_to?(:context) ? value.context : {}
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :items, :mode
|
|
47
|
+
|
|
48
|
+
def initialize(items:, mode:)
|
|
49
|
+
@items = items.freeze
|
|
50
|
+
@mode = mode.to_sym
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def [](key)
|
|
54
|
+
items.fetch(key)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def keys
|
|
58
|
+
items.keys
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def successes
|
|
62
|
+
items.select { |_key, item| item.succeeded? }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def failures
|
|
66
|
+
items.select { |_key, item| item.failed? }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def items_summary
|
|
70
|
+
items.transform_values do |item|
|
|
71
|
+
{
|
|
72
|
+
status: item.status,
|
|
73
|
+
error: item.error&.message
|
|
74
|
+
}.compact
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def failed_items
|
|
79
|
+
failures.transform_values do |item|
|
|
80
|
+
{
|
|
81
|
+
type: item.error.class.name,
|
|
82
|
+
message: item.error.message,
|
|
83
|
+
context: item.error.respond_to?(:context) ? item.error.context : {}
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_h
|
|
89
|
+
items.transform_values(&:to_h)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def summary
|
|
93
|
+
{
|
|
94
|
+
mode: mode,
|
|
95
|
+
total: items.size,
|
|
96
|
+
succeeded: successes.size,
|
|
97
|
+
failed: failures.size,
|
|
98
|
+
status: failures.empty? ? :succeeded : :partial_failure
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def as_json(*)
|
|
103
|
+
{
|
|
104
|
+
mode: mode,
|
|
105
|
+
summary: summary,
|
|
106
|
+
items: to_h
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Igniter
|
|
6
|
+
module Runtime
|
|
7
|
+
class DeferredResult
|
|
8
|
+
attr_reader :token, :payload, :source_node, :waiting_on
|
|
9
|
+
|
|
10
|
+
def initialize(token:, payload: {}, source_node: nil, waiting_on: nil)
|
|
11
|
+
@token = token
|
|
12
|
+
@payload = payload.freeze
|
|
13
|
+
@source_node = source_node&.to_sym
|
|
14
|
+
@waiting_on = waiting_on&.to_sym
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.build(token: nil, payload: {}, source_node: nil, waiting_on: nil)
|
|
18
|
+
new(
|
|
19
|
+
token: token || SecureRandom.uuid,
|
|
20
|
+
payload: payload,
|
|
21
|
+
source_node: source_node,
|
|
22
|
+
waiting_on: waiting_on
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
token: token,
|
|
29
|
+
payload: payload,
|
|
30
|
+
source_node: source_node,
|
|
31
|
+
waiting_on: waiting_on
|
|
32
|
+
}.compact
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def as_json(*)
|
|
36
|
+
to_h
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|