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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +381 -0
  3. data/lib/igniter/extensions/contracts/aggregate_pack.rb +103 -0
  4. data/lib/igniter/extensions/contracts/audit/builder.rb +132 -0
  5. data/lib/igniter/extensions/contracts/audit/event.rb +34 -0
  6. data/lib/igniter/extensions/contracts/audit/snapshot.rb +44 -0
  7. data/lib/igniter/extensions/contracts/audit_pack.rb +60 -0
  8. data/lib/igniter/extensions/contracts/branch_pack.rb +199 -0
  9. data/lib/igniter/extensions/contracts/capabilities/declaration.rb +31 -0
  10. data/lib/igniter/extensions/contracts/capabilities/error.rb +35 -0
  11. data/lib/igniter/extensions/contracts/capabilities/policy.rb +20 -0
  12. data/lib/igniter/extensions/contracts/capabilities/report.rb +47 -0
  13. data/lib/igniter/extensions/contracts/capabilities/violation.rb +30 -0
  14. data/lib/igniter/extensions/contracts/capabilities_pack.rb +146 -0
  15. data/lib/igniter/extensions/contracts/collection_pack.rb +212 -0
  16. data/lib/igniter/extensions/contracts/commerce_pack.rb +91 -0
  17. data/lib/igniter/extensions/contracts/compose_pack.rb +213 -0
  18. data/lib/igniter/extensions/contracts/content_addressing/cache.rb +59 -0
  19. data/lib/igniter/extensions/contracts/content_addressing/content_key.rb +63 -0
  20. data/lib/igniter/extensions/contracts/content_addressing/declaration.rb +47 -0
  21. data/lib/igniter/extensions/contracts/content_addressing_pack.rb +90 -0
  22. data/lib/igniter/extensions/contracts/creator/profile.rb +196 -0
  23. data/lib/igniter/extensions/contracts/creator/report.rb +85 -0
  24. data/lib/igniter/extensions/contracts/creator/scaffold.rb +461 -0
  25. data/lib/igniter/extensions/contracts/creator/scope.rb +79 -0
  26. data/lib/igniter/extensions/contracts/creator/wizard.rb +269 -0
  27. data/lib/igniter/extensions/contracts/creator/workflow.rb +189 -0
  28. data/lib/igniter/extensions/contracts/creator/workflow_step.rb +51 -0
  29. data/lib/igniter/extensions/contracts/creator/write_result.rb +48 -0
  30. data/lib/igniter/extensions/contracts/creator/write_step.rb +63 -0
  31. data/lib/igniter/extensions/contracts/creator/writer.rb +131 -0
  32. data/lib/igniter/extensions/contracts/creator_pack.rb +128 -0
  33. data/lib/igniter/extensions/contracts/dataflow/aggregate_operators.rb +119 -0
  34. data/lib/igniter/extensions/contracts/dataflow/aggregate_state.rb +60 -0
  35. data/lib/igniter/extensions/contracts/dataflow/builder.rb +66 -0
  36. data/lib/igniter/extensions/contracts/dataflow/collection_result.rb +70 -0
  37. data/lib/igniter/extensions/contracts/dataflow/diff.rb +37 -0
  38. data/lib/igniter/extensions/contracts/dataflow/item_result.rb +44 -0
  39. data/lib/igniter/extensions/contracts/dataflow/result.rb +58 -0
  40. data/lib/igniter/extensions/contracts/dataflow/session.rb +173 -0
  41. data/lib/igniter/extensions/contracts/dataflow/window_filter.rb +49 -0
  42. data/lib/igniter/extensions/contracts/dataflow_pack.rb +66 -0
  43. data/lib/igniter/extensions/contracts/debug/pack_audit.rb +181 -0
  44. data/lib/igniter/extensions/contracts/debug/pack_snapshot.rb +46 -0
  45. data/lib/igniter/extensions/contracts/debug/profile_snapshot.rb +50 -0
  46. data/lib/igniter/extensions/contracts/debug/report.rb +50 -0
  47. data/lib/igniter/extensions/contracts/debug_pack.rb +115 -0
  48. data/lib/igniter/extensions/contracts/differential/divergence.rb +37 -0
  49. data/lib/igniter/extensions/contracts/differential/formatter.rb +85 -0
  50. data/lib/igniter/extensions/contracts/differential/report.rb +83 -0
  51. data/lib/igniter/extensions/contracts/differential/runner.rb +136 -0
  52. data/lib/igniter/extensions/contracts/differential_pack.rb +61 -0
  53. data/lib/igniter/extensions/contracts/execution_report_pack.rb +38 -0
  54. data/lib/igniter/extensions/contracts/incremental/formatter.rb +60 -0
  55. data/lib/igniter/extensions/contracts/incremental/node_state.rb +30 -0
  56. data/lib/igniter/extensions/contracts/incremental/result.rb +65 -0
  57. data/lib/igniter/extensions/contracts/incremental/session.rb +146 -0
  58. data/lib/igniter/extensions/contracts/incremental_pack.rb +40 -0
  59. data/lib/igniter/extensions/contracts/invariants/builder.rb +27 -0
  60. data/lib/igniter/extensions/contracts/invariants/cases_report.rb +47 -0
  61. data/lib/igniter/extensions/contracts/invariants/error.rb +34 -0
  62. data/lib/igniter/extensions/contracts/invariants/invariant.rb +30 -0
  63. data/lib/igniter/extensions/contracts/invariants/report.rb +45 -0
  64. data/lib/igniter/extensions/contracts/invariants/suite.rb +36 -0
  65. data/lib/igniter/extensions/contracts/invariants/violation.rb +39 -0
  66. data/lib/igniter/extensions/contracts/invariants_pack.rb +88 -0
  67. data/lib/igniter/extensions/contracts/journal_pack.rb +55 -0
  68. data/lib/igniter/extensions/contracts/language/formula_pack.rb +185 -0
  69. data/lib/igniter/extensions/contracts/language/piecewise_pack.rb +166 -0
  70. data/lib/igniter/extensions/contracts/language/scale_pack.rb +147 -0
  71. data/lib/igniter/extensions/contracts/lookup_pack.rb +50 -0
  72. data/lib/igniter/extensions/contracts/mcp/creator_session.rb +105 -0
  73. data/lib/igniter/extensions/contracts/mcp/tool_argument.rb +35 -0
  74. data/lib/igniter/extensions/contracts/mcp/tool_definition.rb +33 -0
  75. data/lib/igniter/extensions/contracts/mcp/tool_result.rb +28 -0
  76. data/lib/igniter/extensions/contracts/mcp_pack.rb +335 -0
  77. data/lib/igniter/extensions/contracts/provenance/builder.rb +80 -0
  78. data/lib/igniter/extensions/contracts/provenance/lineage.rb +59 -0
  79. data/lib/igniter/extensions/contracts/provenance/node_trace.rb +53 -0
  80. data/lib/igniter/extensions/contracts/provenance/text_formatter.rb +62 -0
  81. data/lib/igniter/extensions/contracts/provenance_pack.rb +52 -0
  82. data/lib/igniter/extensions/contracts/reactive/builder.rb +43 -0
  83. data/lib/igniter/extensions/contracts/reactive/dispatch_result.rb +59 -0
  84. data/lib/igniter/extensions/contracts/reactive/engine.rb +79 -0
  85. data/lib/igniter/extensions/contracts/reactive/event.rb +36 -0
  86. data/lib/igniter/extensions/contracts/reactive/matcher.rb +20 -0
  87. data/lib/igniter/extensions/contracts/reactive/plan.rb +58 -0
  88. data/lib/igniter/extensions/contracts/reactive/subscription.rb +29 -0
  89. data/lib/igniter/extensions/contracts/reactive_pack.rb +169 -0
  90. data/lib/igniter/extensions/contracts/saga/compensation.rb +25 -0
  91. data/lib/igniter/extensions/contracts/saga/compensation_record.rb +28 -0
  92. data/lib/igniter/extensions/contracts/saga/compensation_set.rb +47 -0
  93. data/lib/igniter/extensions/contracts/saga/formatter.rb +39 -0
  94. data/lib/igniter/extensions/contracts/saga/result.rb +56 -0
  95. data/lib/igniter/extensions/contracts/saga/runner.rb +124 -0
  96. data/lib/igniter/extensions/contracts/saga_pack.rb +56 -0
  97. data/lib/igniter/extensions/contracts.rb +445 -0
  98. data/lib/igniter/extensions.rb +6 -0
  99. data/lib/igniter-extensions.rb +3 -0
  100. 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