igniter-extensions 0.5.2
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/README.md +381 -0
- data/lib/igniter/extensions/contracts/aggregate_pack.rb +103 -0
- data/lib/igniter/extensions/contracts/audit/builder.rb +132 -0
- data/lib/igniter/extensions/contracts/audit/event.rb +34 -0
- data/lib/igniter/extensions/contracts/audit/snapshot.rb +44 -0
- data/lib/igniter/extensions/contracts/audit_pack.rb +60 -0
- data/lib/igniter/extensions/contracts/branch_pack.rb +199 -0
- data/lib/igniter/extensions/contracts/capabilities/declaration.rb +31 -0
- data/lib/igniter/extensions/contracts/capabilities/error.rb +35 -0
- data/lib/igniter/extensions/contracts/capabilities/policy.rb +20 -0
- data/lib/igniter/extensions/contracts/capabilities/report.rb +47 -0
- data/lib/igniter/extensions/contracts/capabilities/violation.rb +30 -0
- data/lib/igniter/extensions/contracts/capabilities_pack.rb +146 -0
- data/lib/igniter/extensions/contracts/collection_pack.rb +212 -0
- data/lib/igniter/extensions/contracts/commerce_pack.rb +91 -0
- data/lib/igniter/extensions/contracts/compose_pack.rb +213 -0
- data/lib/igniter/extensions/contracts/content_addressing/cache.rb +59 -0
- data/lib/igniter/extensions/contracts/content_addressing/content_key.rb +63 -0
- data/lib/igniter/extensions/contracts/content_addressing/declaration.rb +47 -0
- data/lib/igniter/extensions/contracts/content_addressing_pack.rb +90 -0
- data/lib/igniter/extensions/contracts/creator/profile.rb +196 -0
- data/lib/igniter/extensions/contracts/creator/report.rb +85 -0
- data/lib/igniter/extensions/contracts/creator/scaffold.rb +461 -0
- data/lib/igniter/extensions/contracts/creator/scope.rb +79 -0
- data/lib/igniter/extensions/contracts/creator/wizard.rb +269 -0
- data/lib/igniter/extensions/contracts/creator/workflow.rb +189 -0
- data/lib/igniter/extensions/contracts/creator/workflow_step.rb +51 -0
- data/lib/igniter/extensions/contracts/creator/write_result.rb +48 -0
- data/lib/igniter/extensions/contracts/creator/write_step.rb +63 -0
- data/lib/igniter/extensions/contracts/creator/writer.rb +131 -0
- data/lib/igniter/extensions/contracts/creator_pack.rb +128 -0
- data/lib/igniter/extensions/contracts/dataflow/aggregate_operators.rb +119 -0
- data/lib/igniter/extensions/contracts/dataflow/aggregate_state.rb +60 -0
- data/lib/igniter/extensions/contracts/dataflow/builder.rb +66 -0
- data/lib/igniter/extensions/contracts/dataflow/collection_result.rb +70 -0
- data/lib/igniter/extensions/contracts/dataflow/diff.rb +37 -0
- data/lib/igniter/extensions/contracts/dataflow/item_result.rb +44 -0
- data/lib/igniter/extensions/contracts/dataflow/result.rb +58 -0
- data/lib/igniter/extensions/contracts/dataflow/session.rb +173 -0
- data/lib/igniter/extensions/contracts/dataflow/window_filter.rb +49 -0
- data/lib/igniter/extensions/contracts/dataflow_pack.rb +66 -0
- data/lib/igniter/extensions/contracts/debug/pack_audit.rb +181 -0
- data/lib/igniter/extensions/contracts/debug/pack_snapshot.rb +46 -0
- data/lib/igniter/extensions/contracts/debug/profile_snapshot.rb +50 -0
- data/lib/igniter/extensions/contracts/debug/report.rb +50 -0
- data/lib/igniter/extensions/contracts/debug_pack.rb +115 -0
- data/lib/igniter/extensions/contracts/differential/divergence.rb +37 -0
- data/lib/igniter/extensions/contracts/differential/formatter.rb +85 -0
- data/lib/igniter/extensions/contracts/differential/report.rb +83 -0
- data/lib/igniter/extensions/contracts/differential/runner.rb +136 -0
- data/lib/igniter/extensions/contracts/differential_pack.rb +61 -0
- data/lib/igniter/extensions/contracts/execution_report_pack.rb +38 -0
- data/lib/igniter/extensions/contracts/incremental/formatter.rb +60 -0
- data/lib/igniter/extensions/contracts/incremental/node_state.rb +30 -0
- data/lib/igniter/extensions/contracts/incremental/result.rb +65 -0
- data/lib/igniter/extensions/contracts/incremental/session.rb +146 -0
- data/lib/igniter/extensions/contracts/incremental_pack.rb +40 -0
- data/lib/igniter/extensions/contracts/invariants/builder.rb +27 -0
- data/lib/igniter/extensions/contracts/invariants/cases_report.rb +47 -0
- data/lib/igniter/extensions/contracts/invariants/error.rb +34 -0
- data/lib/igniter/extensions/contracts/invariants/invariant.rb +30 -0
- data/lib/igniter/extensions/contracts/invariants/report.rb +45 -0
- data/lib/igniter/extensions/contracts/invariants/suite.rb +36 -0
- data/lib/igniter/extensions/contracts/invariants/violation.rb +39 -0
- data/lib/igniter/extensions/contracts/invariants_pack.rb +88 -0
- data/lib/igniter/extensions/contracts/journal_pack.rb +55 -0
- data/lib/igniter/extensions/contracts/language/formula_pack.rb +185 -0
- data/lib/igniter/extensions/contracts/language/piecewise_pack.rb +166 -0
- data/lib/igniter/extensions/contracts/language/scale_pack.rb +147 -0
- data/lib/igniter/extensions/contracts/lookup_pack.rb +50 -0
- data/lib/igniter/extensions/contracts/mcp/creator_session.rb +105 -0
- data/lib/igniter/extensions/contracts/mcp/tool_argument.rb +35 -0
- data/lib/igniter/extensions/contracts/mcp/tool_definition.rb +33 -0
- data/lib/igniter/extensions/contracts/mcp/tool_result.rb +28 -0
- data/lib/igniter/extensions/contracts/mcp_pack.rb +335 -0
- data/lib/igniter/extensions/contracts/provenance/builder.rb +80 -0
- data/lib/igniter/extensions/contracts/provenance/lineage.rb +59 -0
- data/lib/igniter/extensions/contracts/provenance/node_trace.rb +53 -0
- data/lib/igniter/extensions/contracts/provenance/text_formatter.rb +62 -0
- data/lib/igniter/extensions/contracts/provenance_pack.rb +52 -0
- data/lib/igniter/extensions/contracts/reactive/builder.rb +43 -0
- data/lib/igniter/extensions/contracts/reactive/dispatch_result.rb +59 -0
- data/lib/igniter/extensions/contracts/reactive/engine.rb +79 -0
- data/lib/igniter/extensions/contracts/reactive/event.rb +36 -0
- data/lib/igniter/extensions/contracts/reactive/matcher.rb +20 -0
- data/lib/igniter/extensions/contracts/reactive/plan.rb +58 -0
- data/lib/igniter/extensions/contracts/reactive/subscription.rb +29 -0
- data/lib/igniter/extensions/contracts/reactive_pack.rb +169 -0
- data/lib/igniter/extensions/contracts/saga/compensation.rb +25 -0
- data/lib/igniter/extensions/contracts/saga/compensation_record.rb +28 -0
- data/lib/igniter/extensions/contracts/saga/compensation_set.rb +47 -0
- data/lib/igniter/extensions/contracts/saga/formatter.rb +39 -0
- data/lib/igniter/extensions/contracts/saga/result.rb +56 -0
- data/lib/igniter/extensions/contracts/saga/runner.rb +124 -0
- data/lib/igniter/extensions/contracts/saga_pack.rb +56 -0
- data/lib/igniter/extensions/contracts.rb +445 -0
- data/lib/igniter/extensions.rb +6 -0
- data/lib/igniter-extensions.rb +3 -0
- metadata +152 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "provenance/node_trace"
|
|
4
|
+
require_relative "provenance/text_formatter"
|
|
5
|
+
require_relative "provenance/lineage"
|
|
6
|
+
require_relative "provenance/builder"
|
|
7
|
+
|
|
8
|
+
module Igniter
|
|
9
|
+
module Extensions
|
|
10
|
+
module Contracts
|
|
11
|
+
module ProvenancePack
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
REPORT_CONTRIBUTOR = Module.new do
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def augment(report:, result:, profile:) # rubocop:disable Lint/UnusedMethodArgument
|
|
18
|
+
summary = result.outputs.keys.sort.each_with_object({}) do |output_name, memo|
|
|
19
|
+
lineage = Igniter::Extensions::Contracts::ProvenancePack.lineage(result, output_name)
|
|
20
|
+
memo[output_name] = {
|
|
21
|
+
value: lineage.value,
|
|
22
|
+
contributing_inputs: lineage.contributing_inputs
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
report.add_section(:provenance, { outputs: summary })
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def manifest
|
|
31
|
+
Igniter::Contracts::PackManifest.new(
|
|
32
|
+
name: :extensions_provenance,
|
|
33
|
+
registry_contracts: [Igniter::Contracts::PackManifest.diagnostic(:provenance)]
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def install_into(kernel)
|
|
38
|
+
kernel.diagnostics_contributors.register(:provenance, REPORT_CONTRIBUTOR)
|
|
39
|
+
kernel
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def lineage(result, output_name)
|
|
43
|
+
Provenance::Builder.build(output_name, result)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def explain(result, output_name)
|
|
47
|
+
lineage(result, output_name).explain
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Reactive
|
|
7
|
+
class Builder
|
|
8
|
+
attr_reader :plan
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@plan = Plan.new(subscriptions: [])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def react_to(event_type, path: nil, once_per_dispatch: false, &block)
|
|
15
|
+
@plan = plan.react_to(event_type, path: path, once_per_dispatch: once_per_dispatch, &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def effect(path, &block)
|
|
19
|
+
@plan = plan.effect(path, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_success(path = nil, &block)
|
|
23
|
+
@plan = plan.on_success(path, &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_failure(&block)
|
|
27
|
+
@plan = plan.on_failure(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def on_exit(&block)
|
|
31
|
+
@plan = plan.on_exit(&block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.build(&block)
|
|
35
|
+
builder = new
|
|
36
|
+
builder.instance_eval(&block) if block
|
|
37
|
+
builder.plan
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Reactive
|
|
7
|
+
class DispatchResult
|
|
8
|
+
attr_reader :status, :events, :errors, :result, :execution_result, :execution_error
|
|
9
|
+
|
|
10
|
+
def initialize(status:, events:, errors:, result:, execution_result:, execution_error: nil)
|
|
11
|
+
@status = status.to_sym
|
|
12
|
+
@events = events.freeze
|
|
13
|
+
@errors = errors.freeze
|
|
14
|
+
@result = result
|
|
15
|
+
@execution_result = execution_result
|
|
16
|
+
@execution_error = execution_error
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def success?
|
|
21
|
+
status == :succeeded
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def failed?
|
|
25
|
+
status == :failed
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def output(name)
|
|
29
|
+
execution_result&.output(name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_h
|
|
33
|
+
{
|
|
34
|
+
status: status,
|
|
35
|
+
success: success?,
|
|
36
|
+
events: events.map(&:to_h),
|
|
37
|
+
errors: errors.map do |entry|
|
|
38
|
+
{
|
|
39
|
+
event: entry.fetch(:event).to_h,
|
|
40
|
+
subscription: entry.fetch(:subscription).to_h,
|
|
41
|
+
error: {
|
|
42
|
+
type: entry.fetch(:error).class.name,
|
|
43
|
+
message: entry.fetch(:error).message
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
end,
|
|
47
|
+
execution_error: execution_error && {
|
|
48
|
+
type: execution_error.class.name,
|
|
49
|
+
message: execution_error.message
|
|
50
|
+
},
|
|
51
|
+
result: result&.to_h,
|
|
52
|
+
execution_result: execution_result&.to_h
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Igniter
|
|
6
|
+
module Extensions
|
|
7
|
+
module Contracts
|
|
8
|
+
module Reactive
|
|
9
|
+
class Engine
|
|
10
|
+
attr_reader :plan, :errors
|
|
11
|
+
|
|
12
|
+
def initialize(plan:)
|
|
13
|
+
@plan = plan
|
|
14
|
+
@errors = []
|
|
15
|
+
@fired = Set.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(events:, result:, execution_result:, execution_error: nil)
|
|
19
|
+
events.each do |event|
|
|
20
|
+
plan.subscriptions.each do |subscription|
|
|
21
|
+
next unless Matcher.match?(subscription, event)
|
|
22
|
+
next if already_fired?(subscription, event)
|
|
23
|
+
|
|
24
|
+
mark_fired(subscription, event)
|
|
25
|
+
call_action(
|
|
26
|
+
subscription.action,
|
|
27
|
+
event: event,
|
|
28
|
+
result: result,
|
|
29
|
+
execution_result: execution_result,
|
|
30
|
+
execution_error: execution_error,
|
|
31
|
+
value: event.value,
|
|
32
|
+
status: event.status,
|
|
33
|
+
outputs: execution_result&.outputs&.to_h
|
|
34
|
+
)
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
errors << {
|
|
37
|
+
event: event,
|
|
38
|
+
subscription: subscription,
|
|
39
|
+
error: e
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def call_action(action, **kwargs)
|
|
48
|
+
parameters = action.parameters
|
|
49
|
+
accepts_any_keywords = parameters.any? { |kind, _name| kind == :keyrest }
|
|
50
|
+
|
|
51
|
+
if accepts_any_keywords
|
|
52
|
+
action.call(**kwargs)
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
accepted = parameters.select { |kind, _name| %i[key keyreq].include?(kind) }.map(&:last)
|
|
57
|
+
action.call(**kwargs.slice(*accepted))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def already_fired?(subscription, event)
|
|
61
|
+
return false unless subscription.once_per_dispatch
|
|
62
|
+
|
|
63
|
+
@fired.include?(firing_key(subscription, event))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def mark_fired(subscription, event)
|
|
67
|
+
return unless subscription.once_per_dispatch
|
|
68
|
+
|
|
69
|
+
@fired << firing_key(subscription, event)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def firing_key(subscription, event)
|
|
73
|
+
[subscription.object_id, event.type, event.path]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Reactive
|
|
7
|
+
class Event
|
|
8
|
+
attr_reader :event_id, :type, :path, :status, :payload
|
|
9
|
+
|
|
10
|
+
def initialize(event_id:, type:, path:, status:, payload: {})
|
|
11
|
+
@event_id = event_id.to_s
|
|
12
|
+
@type = type.to_sym
|
|
13
|
+
@path = path&.to_sym
|
|
14
|
+
@status = status.to_sym
|
|
15
|
+
@payload = payload.freeze
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def value
|
|
20
|
+
payload[:value]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_h
|
|
24
|
+
{
|
|
25
|
+
event_id: event_id,
|
|
26
|
+
type: type,
|
|
27
|
+
path: path,
|
|
28
|
+
status: status,
|
|
29
|
+
payload: payload
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Reactive
|
|
7
|
+
module Matcher
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def match?(subscription, event)
|
|
11
|
+
return false unless subscription.event_type == event.type
|
|
12
|
+
return true if subscription.path.nil?
|
|
13
|
+
|
|
14
|
+
subscription.path == event.path
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Reactive
|
|
7
|
+
class Plan
|
|
8
|
+
attr_reader :subscriptions
|
|
9
|
+
|
|
10
|
+
def initialize(subscriptions:)
|
|
11
|
+
@subscriptions = subscriptions.freeze
|
|
12
|
+
freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def react_to(event_type, path: nil, once_per_dispatch: false, &block)
|
|
16
|
+
with_subscription(
|
|
17
|
+
Subscription.new(
|
|
18
|
+
event_type: event_type,
|
|
19
|
+
path: path,
|
|
20
|
+
action: block,
|
|
21
|
+
once_per_dispatch: once_per_dispatch
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def effect(path, &block)
|
|
27
|
+
react_to(:output_produced, path: path, &block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def on_success(path = nil, &block)
|
|
31
|
+
event_type = path.nil? ? :execution_succeeded : :output_produced
|
|
32
|
+
react_to(event_type, path: path, once_per_dispatch: true, &block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def on_failure(&block)
|
|
36
|
+
react_to(:execution_failed, once_per_dispatch: true, &block)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def on_exit(&block)
|
|
40
|
+
react_to(:execution_exited, once_per_dispatch: true, &block)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_h
|
|
44
|
+
{
|
|
45
|
+
subscriptions: subscriptions.map(&:to_h)
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def with_subscription(subscription)
|
|
52
|
+
self.class.new(subscriptions: subscriptions + [subscription])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Reactive
|
|
7
|
+
class Subscription
|
|
8
|
+
attr_reader :event_type, :path, :action, :once_per_dispatch
|
|
9
|
+
|
|
10
|
+
def initialize(event_type:, action:, path: nil, once_per_dispatch: false)
|
|
11
|
+
@event_type = event_type.to_sym
|
|
12
|
+
@path = path&.to_sym
|
|
13
|
+
@action = action
|
|
14
|
+
@once_per_dispatch = once_per_dispatch
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
{
|
|
20
|
+
event_type: event_type,
|
|
21
|
+
path: path,
|
|
22
|
+
once_per_dispatch: once_per_dispatch
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "reactive/event"
|
|
4
|
+
require_relative "reactive/subscription"
|
|
5
|
+
require_relative "reactive/matcher"
|
|
6
|
+
require_relative "reactive/plan"
|
|
7
|
+
require_relative "reactive/builder"
|
|
8
|
+
require_relative "reactive/dispatch_result"
|
|
9
|
+
require_relative "reactive/engine"
|
|
10
|
+
|
|
11
|
+
module Igniter
|
|
12
|
+
module Extensions
|
|
13
|
+
module Contracts
|
|
14
|
+
module ReactivePack
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def manifest
|
|
18
|
+
Igniter::Contracts::PackManifest.new(
|
|
19
|
+
name: :extensions_reactive,
|
|
20
|
+
metadata: { category: :orchestration }
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def install_into(kernel)
|
|
25
|
+
kernel
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def build(&block)
|
|
29
|
+
Reactive::Builder.build(&block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def dispatch(target, reactions:)
|
|
33
|
+
result = target
|
|
34
|
+
execution_result = unwrap_execution_result(target)
|
|
35
|
+
events = build_events(target, execution_result: execution_result)
|
|
36
|
+
|
|
37
|
+
engine = Reactive::Engine.new(plan: reactions)
|
|
38
|
+
engine.call(events: events, result: result, execution_result: execution_result)
|
|
39
|
+
|
|
40
|
+
Reactive::DispatchResult.new(
|
|
41
|
+
status: :succeeded,
|
|
42
|
+
events: events,
|
|
43
|
+
errors: engine.errors,
|
|
44
|
+
result: result,
|
|
45
|
+
execution_result: execution_result
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def run(environment, inputs:, reactions:, compiled_graph: nil, &block)
|
|
50
|
+
graph =
|
|
51
|
+
if block
|
|
52
|
+
environment.compile(&block)
|
|
53
|
+
else
|
|
54
|
+
compiled_graph || raise(ArgumentError, "reactive run requires a block or compiled_graph")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
result = environment.execute(graph, inputs: inputs)
|
|
59
|
+
dispatch(result, reactions: reactions)
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
dispatch_failure(e, reactions: reactions)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def run_incremental(session, inputs:, reactions:)
|
|
66
|
+
result = session.run(inputs: inputs)
|
|
67
|
+
dispatch(result, reactions: reactions)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def dispatch_failure(error, reactions:)
|
|
71
|
+
events = [
|
|
72
|
+
Reactive::Event.new(
|
|
73
|
+
event_id: "execution_failed",
|
|
74
|
+
type: :execution_failed,
|
|
75
|
+
path: :execution,
|
|
76
|
+
status: :failed,
|
|
77
|
+
payload: {
|
|
78
|
+
error_type: error.class.name,
|
|
79
|
+
error_message: error.message
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
Reactive::Event.new(
|
|
83
|
+
event_id: "execution_exited",
|
|
84
|
+
type: :execution_exited,
|
|
85
|
+
path: :execution,
|
|
86
|
+
status: :failed,
|
|
87
|
+
payload: {
|
|
88
|
+
error_type: error.class.name,
|
|
89
|
+
error_message: error.message
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
engine = Reactive::Engine.new(plan: reactions)
|
|
95
|
+
engine.call(events: events, result: nil, execution_result: nil, execution_error: error)
|
|
96
|
+
|
|
97
|
+
Reactive::DispatchResult.new(
|
|
98
|
+
status: :failed,
|
|
99
|
+
events: events,
|
|
100
|
+
errors: engine.errors,
|
|
101
|
+
result: nil,
|
|
102
|
+
execution_result: nil,
|
|
103
|
+
execution_error: error
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def unwrap_execution_result(target)
|
|
108
|
+
return target.execution_result if target.respond_to?(:execution_result)
|
|
109
|
+
|
|
110
|
+
target
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def build_events(target, execution_result:)
|
|
114
|
+
events = []
|
|
115
|
+
|
|
116
|
+
events << Reactive::Event.new(
|
|
117
|
+
event_id: "execution_succeeded",
|
|
118
|
+
type: :execution_succeeded,
|
|
119
|
+
path: :execution,
|
|
120
|
+
status: :succeeded,
|
|
121
|
+
payload: {
|
|
122
|
+
output_names: execution_result.outputs.keys
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
execution_result.outputs.to_h.each do |name, value|
|
|
127
|
+
events << Reactive::Event.new(
|
|
128
|
+
event_id: "output_produced:#{name}",
|
|
129
|
+
type: :output_produced,
|
|
130
|
+
path: name,
|
|
131
|
+
status: :succeeded,
|
|
132
|
+
payload: {
|
|
133
|
+
value: value
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if target.respond_to?(:changed_outputs)
|
|
139
|
+
target.changed_outputs.each do |name, change|
|
|
140
|
+
events << Reactive::Event.new(
|
|
141
|
+
event_id: "output_changed:#{name}",
|
|
142
|
+
type: :output_changed,
|
|
143
|
+
path: name,
|
|
144
|
+
status: :succeeded,
|
|
145
|
+
payload: {
|
|
146
|
+
value: execution_result.output(name),
|
|
147
|
+
previous_value: change[:from],
|
|
148
|
+
current_value: change[:to]
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
events << Reactive::Event.new(
|
|
155
|
+
event_id: "execution_exited",
|
|
156
|
+
type: :execution_exited,
|
|
157
|
+
path: :execution,
|
|
158
|
+
status: :succeeded,
|
|
159
|
+
payload: {
|
|
160
|
+
output_names: execution_result.outputs.keys
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
events.freeze
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Saga
|
|
7
|
+
class Compensation
|
|
8
|
+
attr_reader :node_name, :block
|
|
9
|
+
|
|
10
|
+
def initialize(node_name, &block)
|
|
11
|
+
raise ArgumentError, "compensate :#{node_name} requires a block" unless block
|
|
12
|
+
|
|
13
|
+
@node_name = node_name.to_sym
|
|
14
|
+
@block = block
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run(inputs:, value:)
|
|
19
|
+
block.call(inputs: inputs, value: value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Saga
|
|
7
|
+
class CompensationRecord
|
|
8
|
+
attr_reader :node_name, :error
|
|
9
|
+
|
|
10
|
+
def initialize(node_name:, success:, error: nil)
|
|
11
|
+
@node_name = node_name.to_sym
|
|
12
|
+
@success = success
|
|
13
|
+
@error = error
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def success?
|
|
18
|
+
@success
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def failed?
|
|
22
|
+
!success?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Saga
|
|
7
|
+
class CompensationSet
|
|
8
|
+
def self.build(&block)
|
|
9
|
+
new.tap do |set|
|
|
10
|
+
set.instance_eval(&block) if block
|
|
11
|
+
set.finalize!
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@compensations = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def compensate(node_name, &block)
|
|
20
|
+
@compensations[node_name.to_sym] = Compensation.new(node_name, &block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def [](node_name)
|
|
24
|
+
@compensations[node_name.to_sym]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def key?(node_name)
|
|
28
|
+
@compensations.key?(node_name.to_sym)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_h
|
|
32
|
+
@compensations.dup
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def keys
|
|
36
|
+
@compensations.keys
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def finalize!
|
|
40
|
+
@compensations.freeze
|
|
41
|
+
freeze
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Saga
|
|
7
|
+
module Formatter
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def format(result)
|
|
11
|
+
lines = []
|
|
12
|
+
lines << "Status: #{result.success? ? "SUCCESS" : "FAILED"}"
|
|
13
|
+
lines << "Profile: #{result.execution_result.profile_fingerprint}"
|
|
14
|
+
|
|
15
|
+
unless result.success?
|
|
16
|
+
lines << "Error: #{result.error.message}"
|
|
17
|
+
lines << "At node: :#{result.failed_node}" if result.failed_node
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
append_compensations(result, lines)
|
|
21
|
+
lines.join("\n")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def append_compensations(result, lines)
|
|
25
|
+
return if result.compensations.empty?
|
|
26
|
+
|
|
27
|
+
lines << ""
|
|
28
|
+
lines << "COMPENSATIONS (#{result.compensations.length}):"
|
|
29
|
+
result.compensations.each do |record|
|
|
30
|
+
tag = record.success? ? "[ok] " : "[fail] "
|
|
31
|
+
lines << " #{tag} :#{record.node_name}"
|
|
32
|
+
lines << " error: #{record.error.message}" if record.failed?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|