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,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ require_relative "write_step"
6
+ require_relative "write_result"
7
+
8
+ module Igniter
9
+ module Extensions
10
+ module Contracts
11
+ module Creator
12
+ class Writer
13
+ VALID_MODES = %i[skip_existing overwrite].freeze
14
+
15
+ attr_reader :workflow, :root, :mode
16
+
17
+ def initialize(workflow:, root:, mode: :skip_existing)
18
+ @workflow = workflow
19
+ @root = File.expand_path(root.to_s)
20
+ @mode = normalize_mode(mode)
21
+ freeze
22
+ end
23
+
24
+ def scaffold
25
+ workflow.scaffold
26
+ end
27
+
28
+ def plan
29
+ WriteResult.new(root: root, mode: mode, steps: plan_steps)
30
+ end
31
+
32
+ def write
33
+ WriteResult.new(root: root, mode: mode, steps: directory_steps + file_steps)
34
+ end
35
+
36
+ private
37
+
38
+ def normalize_mode(mode)
39
+ value = mode.to_sym
40
+ return value if VALID_MODES.include?(value)
41
+
42
+ raise ArgumentError, "unsupported creator writer mode #{mode.inspect}"
43
+ end
44
+
45
+ def plan_steps
46
+ planned_directories + planned_files
47
+ end
48
+
49
+ def planned_directories
50
+ directories.map do |relative_path|
51
+ WriteStep.new(
52
+ kind: :directory,
53
+ relative_path: relative_path,
54
+ absolute_path: absolute_path_for(relative_path),
55
+ status: File.directory?(absolute_path_for(relative_path)) ? :unchanged : :pending
56
+ )
57
+ end
58
+ end
59
+
60
+ def planned_files
61
+ scaffold.files.map do |relative_path, _content|
62
+ absolute_path = absolute_path_for(relative_path)
63
+ WriteStep.new(
64
+ kind: :file,
65
+ relative_path: relative_path,
66
+ absolute_path: absolute_path,
67
+ status: file_plan_status(absolute_path)
68
+ )
69
+ end
70
+ end
71
+
72
+ def directory_steps
73
+ directories.map do |relative_path|
74
+ absolute_path = absolute_path_for(relative_path)
75
+ existed = File.directory?(absolute_path)
76
+ FileUtils.mkdir_p(absolute_path)
77
+ WriteStep.new(
78
+ kind: :directory,
79
+ relative_path: relative_path,
80
+ absolute_path: absolute_path,
81
+ status: existed ? :unchanged : :created
82
+ )
83
+ end
84
+ end
85
+
86
+ def file_steps
87
+ scaffold.files.map do |relative_path, content|
88
+ absolute_path = absolute_path_for(relative_path)
89
+ parent = File.dirname(absolute_path)
90
+ FileUtils.mkdir_p(parent)
91
+
92
+ if File.exist?(absolute_path) && mode == :skip_existing
93
+ WriteStep.new(
94
+ kind: :file,
95
+ relative_path: relative_path,
96
+ absolute_path: absolute_path,
97
+ status: :skipped,
98
+ reason: "existing file preserved"
99
+ )
100
+ else
101
+ status = File.exist?(absolute_path) && File.read(absolute_path) == content ? :unchanged : :written
102
+ File.write(absolute_path, content)
103
+ WriteStep.new(
104
+ kind: :file,
105
+ relative_path: relative_path,
106
+ absolute_path: absolute_path,
107
+ status: status
108
+ )
109
+ end
110
+ end
111
+ end
112
+
113
+ def file_plan_status(absolute_path)
114
+ return :pending unless File.exist?(absolute_path)
115
+ return :skipped if mode == :skip_existing
116
+
117
+ :pending
118
+ end
119
+
120
+ def directories
121
+ scaffold.files.keys.map { |path| File.dirname(path) }.uniq.sort
122
+ end
123
+
124
+ def absolute_path_for(relative_path)
125
+ File.join(root, relative_path)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "creator/profile"
4
+ require_relative "creator/scaffold"
5
+ require_relative "creator/scope"
6
+ require_relative "creator/report"
7
+ require_relative "creator/workflow"
8
+ require_relative "creator/wizard"
9
+
10
+ module Igniter
11
+ module Extensions
12
+ module Contracts
13
+ module CreatorPack
14
+ module_function
15
+
16
+ def manifest
17
+ Igniter::Contracts::PackManifest.new(
18
+ name: :extensions_creator,
19
+ requires_packs: [DebugPack],
20
+ metadata: { category: :developer }
21
+ )
22
+ end
23
+
24
+ def install_into(kernel)
25
+ kernel
26
+ end
27
+
28
+ def available_profiles
29
+ Creator::Profile.available
30
+ end
31
+
32
+ def available_scopes
33
+ Creator::Scope.available
34
+ end
35
+
36
+ def scaffold(name:, kind: nil, namespace: "MyCompany::IgniterPacks", profile: nil, capabilities: nil,
37
+ scope: :monorepo_package)
38
+ authoring_profile = Creator::Profile.build(profile: profile, kind: kind, capabilities: capabilities)
39
+ target_scope = Creator::Scope.build(scope)
40
+ Creator::Scaffold.new(
41
+ name: name,
42
+ kind: authoring_profile.kind,
43
+ namespace: namespace,
44
+ profile: authoring_profile,
45
+ scope: target_scope
46
+ )
47
+ end
48
+
49
+ def report(name:, kind: nil, namespace: "MyCompany::IgniterPacks", profile: nil, capabilities: nil,
50
+ scope: :monorepo_package, pack: nil, target_profile: nil)
51
+ generated = scaffold(
52
+ name: name,
53
+ kind: kind,
54
+ namespace: namespace,
55
+ profile: profile,
56
+ capabilities: capabilities,
57
+ scope: scope
58
+ )
59
+ audit = pack ? DebugPack.audit(pack, profile: target_profile) : nil
60
+
61
+ Creator::Report.new(scaffold: generated, audit: audit)
62
+ end
63
+
64
+ def workflow(name:, kind: nil, namespace: "MyCompany::IgniterPacks", profile: nil, capabilities: nil,
65
+ scope: :monorepo_package, pack: nil, target_profile: nil)
66
+ Creator::Workflow.new(
67
+ report: report(
68
+ name: name,
69
+ kind: kind,
70
+ namespace: namespace,
71
+ profile: profile,
72
+ capabilities: capabilities,
73
+ scope: scope,
74
+ pack: pack,
75
+ target_profile: target_profile
76
+ )
77
+ )
78
+ end
79
+
80
+ def wizard(name: nil, kind: nil, namespace: "MyCompany::IgniterPacks", profile: nil, capabilities: nil,
81
+ scope: nil, root: nil, mode: :skip_existing, pack: nil, target_profile: nil)
82
+ Creator::Wizard.new(
83
+ name: name,
84
+ kind: kind,
85
+ namespace: namespace,
86
+ profile: profile,
87
+ capabilities: capabilities,
88
+ scope: scope,
89
+ root: root,
90
+ mode: mode,
91
+ pack: pack,
92
+ target_profile: target_profile
93
+ )
94
+ end
95
+
96
+ def writer(name:, root:, kind: nil, namespace: "MyCompany::IgniterPacks", profile: nil, capabilities: nil,
97
+ scope: :monorepo_package, pack: nil, target_profile: nil, mode: :skip_existing)
98
+ workflow(
99
+ name: name,
100
+ kind: kind,
101
+ namespace: namespace,
102
+ profile: profile,
103
+ capabilities: capabilities,
104
+ scope: scope,
105
+ pack: pack,
106
+ target_profile: target_profile
107
+ ).writer(root: root, mode: mode)
108
+ end
109
+
110
+ def write(name:, root:, kind: nil, namespace: "MyCompany::IgniterPacks", profile: nil, capabilities: nil,
111
+ scope: :monorepo_package, pack: nil, target_profile: nil, mode: :skip_existing)
112
+ writer(
113
+ name: name,
114
+ kind: kind,
115
+ namespace: namespace,
116
+ profile: profile,
117
+ capabilities: capabilities,
118
+ scope: scope,
119
+ pack: pack,
120
+ target_profile: target_profile,
121
+ root: root,
122
+ mode: mode
123
+ ).write
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ module AggregateOperators
8
+ Operator = Struct.new(
9
+ :initial_fn, :project, :add, :remove, :finalize, :recompute,
10
+ keyword_init: true
11
+ ) do
12
+ def initial
13
+ initial_fn.call
14
+ end
15
+ end
16
+
17
+ module_function
18
+
19
+ def count(filter: nil)
20
+ Operator.new(
21
+ initial_fn: -> { 0 },
22
+ project: ->(item) { filter.nil? || filter.call(item) ? 1 : 0 },
23
+ add: ->(acc, value) { acc + value },
24
+ remove: ->(acc, value) { acc - value },
25
+ finalize: ->(acc, _) { acc },
26
+ recompute: false
27
+ )
28
+ end
29
+
30
+ def sum(projection:)
31
+ Operator.new(
32
+ initial_fn: -> { 0 },
33
+ project: ->(item) { project(item, projection).to_f },
34
+ add: ->(acc, value) { acc + value },
35
+ remove: ->(acc, value) { acc - value },
36
+ finalize: ->(acc, _) { acc },
37
+ recompute: false
38
+ )
39
+ end
40
+
41
+ def avg(projection:)
42
+ Operator.new(
43
+ initial_fn: -> { { sum: 0.0, count: 0 } },
44
+ project: ->(item) { project(item, projection).to_f },
45
+ add: ->(acc, value) { { sum: acc.fetch(:sum) + value, count: acc.fetch(:count) + 1 } },
46
+ remove: ->(acc, value) { { sum: acc.fetch(:sum) - value, count: acc.fetch(:count) - 1 } },
47
+ finalize: lambda { |acc, _|
48
+ acc.fetch(:count).zero? ? 0.0 : acc.fetch(:sum) / acc.fetch(:count)
49
+ },
50
+ recompute: false
51
+ )
52
+ end
53
+
54
+ def min(projection:)
55
+ recomputed_projection(projection, &:min)
56
+ end
57
+
58
+ def max(projection:)
59
+ recomputed_projection(projection, &:max)
60
+ end
61
+
62
+ def group_count(projection:)
63
+ Operator.new(
64
+ initial_fn: -> { {} },
65
+ project: ->(item) { project(item, projection) },
66
+ add: ->(acc, group_key) { acc.merge(group_key => (acc[group_key] || 0) + 1) },
67
+ remove: lambda { |acc, group_key|
68
+ count = (acc[group_key] || 1) - 1
69
+ count <= 0 ? acc.reject { |key, _| key == group_key } : acc.merge(group_key => count)
70
+ },
71
+ finalize: ->(acc, _) { acc },
72
+ recompute: false
73
+ )
74
+ end
75
+
76
+ def custom(initial:, add:, remove:)
77
+ Operator.new(
78
+ initial_fn: -> { duplicate(initial) },
79
+ project: ->(item) { item },
80
+ add: ->(acc, item) { add.call(acc, item) },
81
+ remove: ->(acc, item) { remove.call(acc, item) },
82
+ finalize: ->(acc, _) { acc },
83
+ recompute: false
84
+ )
85
+ end
86
+
87
+ def project(item, projection)
88
+ return item if projection.nil?
89
+ return projection.call(item) if projection.respond_to?(:call)
90
+
91
+ key = projection.to_sym
92
+ return item.output(key) if item.outputs.key?(key)
93
+ return item.input(key) if item.inputs.key?(key)
94
+
95
+ raise KeyError,
96
+ "aggregate projection #{projection.inspect} not present on dataflow item #{item.key.inspect}"
97
+ end
98
+
99
+ def recomputed_projection(projection, &finalizer)
100
+ Operator.new(
101
+ initial_fn: -> { nil },
102
+ project: ->(item) { project(item, projection) },
103
+ add: nil,
104
+ remove: nil,
105
+ finalize: ->(_acc, contributions) { finalizer.call(contributions.values) },
106
+ recompute: true
107
+ )
108
+ end
109
+
110
+ def duplicate(value)
111
+ value.frozen? ? value : value.dup
112
+ rescue TypeError
113
+ value
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ class AggregateState
8
+ def initialize(operator)
9
+ @operator = operator
10
+ @contributions = {}
11
+ @accum = operator.initial
12
+ end
13
+
14
+ def apply_diff!(diff, collection_result)
15
+ diff.changed.each do |key|
16
+ retract!(key)
17
+ contribute!(key, collection_result[key])
18
+ end
19
+
20
+ diff.added.each do |key|
21
+ contribute!(key, collection_result[key])
22
+ end
23
+
24
+ diff.removed.each do |key|
25
+ retract!(key)
26
+ end
27
+ end
28
+
29
+ def value
30
+ if @operator.recompute
31
+ @operator.finalize.call(nil, @contributions)
32
+ else
33
+ @operator.finalize.call(@accum, @contributions.size)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def contribute!(key, item)
40
+ contribution = @operator.project.call(item)
41
+ return if contribution.nil?
42
+
43
+ @contributions[key] = contribution
44
+ return if @operator.recompute
45
+
46
+ @accum = @operator.add.call(@accum, contribution)
47
+ end
48
+
49
+ def retract!(key)
50
+ old_contribution = @contributions.delete(key)
51
+ return unless old_contribution
52
+ return if @operator.recompute
53
+
54
+ @accum = @operator.remove.call(@accum, old_contribution)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ class Builder
8
+ attr_reader :source, :key, :window, :context
9
+
10
+ def initialize(source:, key:, window: nil, context: [])
11
+ @source = source.to_sym
12
+ @key = key.to_sym
13
+ @window = window
14
+ @context = Array(context).map(&:to_sym).freeze
15
+ @item_block = nil
16
+ @aggregate_operators = {}
17
+ end
18
+
19
+ def item(&block)
20
+ @item_block = block
21
+ end
22
+
23
+ def count(name, matching: nil)
24
+ register(name, AggregateOperators.count(filter: matching))
25
+ end
26
+
27
+ def sum(name, using:)
28
+ register(name, AggregateOperators.sum(projection: using))
29
+ end
30
+
31
+ def avg(name, using:)
32
+ register(name, AggregateOperators.avg(projection: using))
33
+ end
34
+
35
+ def min(name, using:)
36
+ register(name, AggregateOperators.min(projection: using))
37
+ end
38
+
39
+ def max(name, using:)
40
+ register(name, AggregateOperators.max(projection: using))
41
+ end
42
+
43
+ def group_count(name, using:)
44
+ register(name, AggregateOperators.group_count(projection: using))
45
+ end
46
+
47
+ def aggregate(name, initial:, add:, remove:)
48
+ register(name, AggregateOperators.custom(initial: initial, add: add, remove: remove))
49
+ end
50
+
51
+ def build!(environment)
52
+ raise Igniter::Contracts::Error, "DataflowPack requires an `item do ... end` definition" unless @item_block
53
+
54
+ [environment.compile(&@item_block), @aggregate_operators.dup.freeze]
55
+ end
56
+
57
+ private
58
+
59
+ def register(name, operator)
60
+ @aggregate_operators[name.to_sym] = operator
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ class CollectionResult
8
+ include Enumerable
9
+
10
+ attr_reader :items, :diff
11
+
12
+ def initialize(items:, diff:)
13
+ @items = items.dup.freeze
14
+ @diff = diff
15
+ freeze
16
+ end
17
+
18
+ def [](key)
19
+ items[key]
20
+ end
21
+
22
+ def fetch(key)
23
+ items.fetch(key)
24
+ end
25
+
26
+ def each(&block)
27
+ items.each(&block)
28
+ end
29
+
30
+ def each_value(&block)
31
+ items.each_value(&block)
32
+ end
33
+
34
+ def keys
35
+ items.keys
36
+ end
37
+
38
+ def values
39
+ items.values
40
+ end
41
+
42
+ def successes
43
+ items
44
+ end
45
+
46
+ def summary
47
+ {
48
+ mode: :incremental,
49
+ total: items.size,
50
+ succeeded: items.size,
51
+ failed: 0,
52
+ status: :success,
53
+ added: diff.added.size,
54
+ removed: diff.removed.size,
55
+ changed: diff.changed.size,
56
+ unchanged: diff.unchanged.size
57
+ }
58
+ end
59
+
60
+ def to_h
61
+ {
62
+ items: items.transform_values(&:to_h),
63
+ diff: diff.to_h
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ Diff = Struct.new(:added, :removed, :changed, :unchanged, keyword_init: true) do
8
+ def any_changes?
9
+ added.any? || removed.any? || changed.any?
10
+ end
11
+
12
+ def processed_count
13
+ added.size + changed.size
14
+ end
15
+
16
+ def explain
17
+ parts = []
18
+ parts << "added(#{added.size}): #{added.inspect}" unless added.empty?
19
+ parts << "removed(#{removed.size}): #{removed.inspect}" unless removed.empty?
20
+ parts << "changed(#{changed.size}): #{changed.inspect}" unless changed.empty?
21
+ parts << "unchanged(#{unchanged.size})" unless unchanged.empty?
22
+ parts.empty? ? "(no changes)" : parts.join(", ")
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ added: added,
28
+ removed: removed,
29
+ changed: changed,
30
+ unchanged: unchanged
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ class ItemResult
8
+ attr_reader :key, :inputs, :execution_result, :incremental_result
9
+
10
+ def initialize(key:, inputs:, execution_result:, incremental_result:)
11
+ @key = key
12
+ @inputs = Igniter::Contracts::NamedValues.new(inputs)
13
+ @execution_result = execution_result
14
+ @incremental_result = incremental_result
15
+ freeze
16
+ end
17
+
18
+ alias result execution_result
19
+
20
+ def input(name)
21
+ inputs.fetch(name)
22
+ end
23
+
24
+ def output(name)
25
+ execution_result.output(name)
26
+ end
27
+
28
+ def outputs
29
+ execution_result.outputs
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ key: key,
35
+ inputs: inputs.to_h,
36
+ execution_result: execution_result.to_h,
37
+ incremental_result: incremental_result.to_h
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end