igniter 0.2.0 → 0.3.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +224 -1
  4. data/docs/API_V2.md +296 -1
  5. data/docs/BACKLOG.md +166 -0
  6. data/docs/BRANCHES_V1.md +213 -0
  7. data/docs/COLLECTIONS_V1.md +303 -0
  8. data/docs/EXECUTION_MODEL_V2.md +79 -0
  9. data/docs/PATTERNS.md +222 -0
  10. data/docs/STORE_ADAPTERS.md +126 -0
  11. data/examples/README.md +127 -0
  12. data/examples/async_store.rb +47 -0
  13. data/examples/collection.rb +43 -0
  14. data/examples/collection_partial_failure.rb +50 -0
  15. data/examples/marketing_ergonomics.rb +57 -0
  16. data/examples/ringcentral_routing.rb +269 -0
  17. data/lib/igniter/compiler/compiled_graph.rb +90 -0
  18. data/lib/igniter/compiler/graph_compiler.rb +12 -2
  19. data/lib/igniter/compiler/type_resolver.rb +54 -0
  20. data/lib/igniter/compiler/validation_context.rb +61 -0
  21. data/lib/igniter/compiler/validation_pipeline.rb +30 -0
  22. data/lib/igniter/compiler/validator.rb +1 -187
  23. data/lib/igniter/compiler/validators/callable_validator.rb +107 -0
  24. data/lib/igniter/compiler/validators/dependencies_validator.rb +153 -0
  25. data/lib/igniter/compiler/validators/outputs_validator.rb +66 -0
  26. data/lib/igniter/compiler/validators/type_compatibility_validator.rb +84 -0
  27. data/lib/igniter/compiler/validators/uniqueness_validator.rb +60 -0
  28. data/lib/igniter/compiler.rb +8 -0
  29. data/lib/igniter/contract.rb +152 -4
  30. data/lib/igniter/diagnostics/auditing/report/console_formatter.rb +80 -0
  31. data/lib/igniter/diagnostics/auditing/report/markdown_formatter.rb +22 -0
  32. data/lib/igniter/diagnostics/introspection/formatters/mermaid_formatter.rb +58 -0
  33. data/lib/igniter/diagnostics/introspection/formatters/text_tree_formatter.rb +44 -0
  34. data/lib/igniter/diagnostics/report.rb +186 -11
  35. data/lib/igniter/dsl/contract_builder.rb +271 -5
  36. data/lib/igniter/dsl/schema_builder.rb +73 -0
  37. data/lib/igniter/dsl.rb +1 -0
  38. data/lib/igniter/errors.rb +11 -0
  39. data/lib/igniter/events/bus.rb +5 -0
  40. data/lib/igniter/events/event.rb +29 -0
  41. data/lib/igniter/executor.rb +74 -0
  42. data/lib/igniter/executor_registry.rb +44 -0
  43. data/lib/igniter/extensions/auditing/timeline.rb +4 -0
  44. data/lib/igniter/extensions/introspection/graph_formatter.rb +33 -3
  45. data/lib/igniter/extensions/introspection/plan_formatter.rb +55 -0
  46. data/lib/igniter/extensions/introspection/runtime_formatter.rb +18 -3
  47. data/lib/igniter/extensions/introspection.rb +1 -0
  48. data/lib/igniter/extensions/reactive/engine.rb +49 -2
  49. data/lib/igniter/extensions/reactive/reaction.rb +3 -2
  50. data/lib/igniter/model/branch_node.rb +46 -0
  51. data/lib/igniter/model/collection_node.rb +31 -0
  52. data/lib/igniter/model/composition_node.rb +2 -2
  53. data/lib/igniter/model/compute_node.rb +58 -2
  54. data/lib/igniter/model/input_node.rb +2 -2
  55. data/lib/igniter/model/output_node.rb +24 -4
  56. data/lib/igniter/model.rb +2 -0
  57. data/lib/igniter/runtime/cache.rb +64 -25
  58. data/lib/igniter/runtime/collection_result.rb +111 -0
  59. data/lib/igniter/runtime/deferred_result.rb +40 -0
  60. data/lib/igniter/runtime/execution.rb +261 -11
  61. data/lib/igniter/runtime/input_validator.rb +2 -24
  62. data/lib/igniter/runtime/invalidator.rb +1 -1
  63. data/lib/igniter/runtime/job_worker.rb +18 -0
  64. data/lib/igniter/runtime/node_state.rb +20 -0
  65. data/lib/igniter/runtime/planner.rb +126 -0
  66. data/lib/igniter/runtime/resolver.rb +310 -15
  67. data/lib/igniter/runtime/result.rb +14 -2
  68. data/lib/igniter/runtime/runner_factory.rb +20 -0
  69. data/lib/igniter/runtime/runners/inline_runner.rb +21 -0
  70. data/lib/igniter/runtime/runners/store_runner.rb +29 -0
  71. data/lib/igniter/runtime/runners/thread_pool_runner.rb +37 -0
  72. data/lib/igniter/runtime/stores/active_record_store.rb +41 -0
  73. data/lib/igniter/runtime/stores/file_store.rb +43 -0
  74. data/lib/igniter/runtime/stores/memory_store.rb +40 -0
  75. data/lib/igniter/runtime/stores/redis_store.rb +44 -0
  76. data/lib/igniter/runtime.rb +12 -0
  77. data/lib/igniter/type_system.rb +44 -0
  78. data/lib/igniter/version.rb +1 -1
  79. data/lib/igniter.rb +23 -0
  80. metadata +43 -2
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Runtime
5
+ module Runners
6
+ class InlineRunner
7
+ def initialize(execution, resolver:, max_workers: nil)
8
+ @execution = execution
9
+ @resolver = resolver
10
+ @max_workers = max_workers
11
+ end
12
+
13
+ def run(node_names)
14
+ Array(node_names).each do |node_name|
15
+ @resolver.resolve(node_name)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Runtime
5
+ module Runners
6
+ class StoreRunner
7
+ def initialize(execution, resolver:, store:, max_workers: nil)
8
+ @execution = execution
9
+ @delegate = InlineRunner.new(execution, resolver: resolver, max_workers: max_workers)
10
+ @store = store
11
+ end
12
+
13
+ def run(node_names)
14
+ @delegate.run(node_names)
15
+ end
16
+
17
+ def persist!
18
+ return unless @store
19
+
20
+ if @execution.pending?
21
+ @store.save(@execution.snapshot(include_resolution: false))
22
+ else
23
+ @store.delete(@execution.events.execution_id)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+
5
+ module Igniter
6
+ module Runtime
7
+ module Runners
8
+ class ThreadPoolRunner
9
+ def initialize(execution, resolver:, max_workers: nil)
10
+ @execution = execution
11
+ @resolver = resolver
12
+ @max_workers = [Integer(max_workers || 4), 1].max
13
+ end
14
+
15
+ def run(node_names)
16
+ queue = Queue.new
17
+ Array(node_names).each { |node_name| queue << node_name }
18
+ worker_count = [@max_workers, queue.size].min
19
+ return if worker_count.zero?
20
+
21
+ threads = worker_count.times.map do
22
+ Thread.new do
23
+ loop do
24
+ node_name = queue.pop(true)
25
+ @resolver.resolve(node_name)
26
+ rescue ThreadError
27
+ break
28
+ end
29
+ end
30
+ end
31
+
32
+ threads.each(&:join)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Igniter
6
+ module Runtime
7
+ module Stores
8
+ class ActiveRecordStore
9
+ def initialize(record_class:, execution_id_column: :execution_id, snapshot_column: :snapshot_json)
10
+ @record_class = record_class
11
+ @execution_id_column = execution_id_column.to_sym
12
+ @snapshot_column = snapshot_column.to_sym
13
+ end
14
+
15
+ def save(snapshot)
16
+ execution_id = snapshot[:execution_id] || snapshot["execution_id"]
17
+ record = @record_class.find_or_initialize_by(@execution_id_column => execution_id)
18
+ record.public_send(:"#{@snapshot_column}=", JSON.generate(snapshot))
19
+ record.save!
20
+ execution_id
21
+ end
22
+
23
+ def fetch(execution_id)
24
+ record = @record_class.find_by(@execution_id_column => execution_id)
25
+ raise Igniter::ResolutionError, "No execution snapshot found for '#{execution_id}'" unless record
26
+
27
+ JSON.parse(record.public_send(@snapshot_column))
28
+ end
29
+
30
+ def delete(execution_id)
31
+ record = @record_class.find_by(@execution_id_column => execution_id)
32
+ record&.destroy!
33
+ end
34
+
35
+ def exist?(execution_id)
36
+ !!@record_class.find_by(@execution_id_column => execution_id)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+
6
+ module Igniter
7
+ module Runtime
8
+ module Stores
9
+ class FileStore
10
+ def initialize(root:)
11
+ @root = root
12
+ FileUtils.mkdir_p(@root)
13
+ end
14
+
15
+ def save(snapshot)
16
+ execution_id = snapshot[:execution_id] || snapshot["execution_id"]
17
+ File.write(path_for(execution_id), JSON.pretty_generate(snapshot))
18
+ execution_id
19
+ end
20
+
21
+ def fetch(execution_id)
22
+ JSON.parse(File.read(path_for(execution_id)))
23
+ rescue Errno::ENOENT
24
+ raise Igniter::ResolutionError, "No execution snapshot found for '#{execution_id}'"
25
+ end
26
+
27
+ def delete(execution_id)
28
+ FileUtils.rm_f(path_for(execution_id))
29
+ end
30
+
31
+ def exist?(execution_id)
32
+ File.exist?(path_for(execution_id))
33
+ end
34
+
35
+ private
36
+
37
+ def path_for(execution_id)
38
+ File.join(@root, "#{execution_id}.json")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Runtime
5
+ module Stores
6
+ class MemoryStore
7
+ def initialize
8
+ @snapshots = {}
9
+ @mutex = Mutex.new
10
+ end
11
+
12
+ def save(snapshot)
13
+ execution_id = snapshot[:execution_id] || snapshot["execution_id"]
14
+ @mutex.synchronize { @snapshots[execution_id] = deep_copy(snapshot) }
15
+ execution_id
16
+ end
17
+
18
+ def fetch(execution_id)
19
+ @mutex.synchronize { deep_copy(@snapshots.fetch(execution_id)) }
20
+ rescue KeyError
21
+ raise Igniter::ResolutionError, "No execution snapshot found for '#{execution_id}'"
22
+ end
23
+
24
+ def delete(execution_id)
25
+ @mutex.synchronize { @snapshots.delete(execution_id) }
26
+ end
27
+
28
+ def exist?(execution_id)
29
+ @mutex.synchronize { @snapshots.key?(execution_id) }
30
+ end
31
+
32
+ private
33
+
34
+ def deep_copy(value)
35
+ Marshal.load(Marshal.dump(value))
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Igniter
6
+ module Runtime
7
+ module Stores
8
+ class RedisStore
9
+ def initialize(redis:, namespace: "igniter:executions")
10
+ @redis = redis
11
+ @namespace = namespace
12
+ end
13
+
14
+ def save(snapshot)
15
+ execution_id = snapshot[:execution_id] || snapshot["execution_id"]
16
+ @redis.set(redis_key(execution_id), JSON.generate(snapshot))
17
+ execution_id
18
+ end
19
+
20
+ def fetch(execution_id)
21
+ payload = @redis.get(redis_key(execution_id))
22
+ raise Igniter::ResolutionError, "No execution snapshot found for '#{execution_id}'" unless payload
23
+
24
+ JSON.parse(payload)
25
+ end
26
+
27
+ def delete(execution_id)
28
+ @redis.del(redis_key(execution_id))
29
+ end
30
+
31
+ def exist?(execution_id)
32
+ result = @redis.exists?(redis_key(execution_id))
33
+ result == true || result.to_i.positive?
34
+ end
35
+
36
+ private
37
+
38
+ def redis_key(execution_id)
39
+ "#{@namespace}:#{execution_id}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -2,7 +2,19 @@
2
2
 
3
3
  require_relative "runtime/node_state"
4
4
  require_relative "runtime/cache"
5
+ require_relative "runtime/deferred_result"
6
+ require_relative "runtime/collection_result"
5
7
  require_relative "runtime/input_validator"
8
+ require_relative "runtime/planner"
9
+ require_relative "runtime/job_worker"
10
+ require_relative "runtime/runners/inline_runner"
11
+ require_relative "runtime/runners/store_runner"
12
+ require_relative "runtime/runners/thread_pool_runner"
13
+ require_relative "runtime/stores/active_record_store"
14
+ require_relative "runtime/stores/file_store"
15
+ require_relative "runtime/stores/memory_store"
16
+ require_relative "runtime/stores/redis_store"
17
+ require_relative "runtime/runner_factory"
6
18
  require_relative "runtime/resolver"
7
19
  require_relative "runtime/invalidator"
8
20
  require_relative "runtime/result"
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module TypeSystem
5
+ SUPPORTED_TYPES = {
6
+ integer: Integer,
7
+ float: Float,
8
+ numeric: Numeric,
9
+ string: String,
10
+ boolean: :boolean,
11
+ array: Array,
12
+ hash: Hash,
13
+ symbol: Symbol,
14
+ result: :result
15
+ }.freeze
16
+
17
+ module_function
18
+
19
+ def supported?(type)
20
+ return false unless type
21
+
22
+ SUPPORTED_TYPES.key?(type.to_sym)
23
+ end
24
+
25
+ def compatible?(source_type, target_type)
26
+ return true if source_type.nil? || target_type.nil?
27
+
28
+ source = source_type.to_sym
29
+ target = target_type.to_sym
30
+ return true if source == target
31
+ return true if %i[integer float].include?(source) && target == :numeric
32
+
33
+ false
34
+ end
35
+
36
+ def match?(type, value)
37
+ matcher = SUPPORTED_TYPES.fetch(type.to_sym)
38
+ return false if matcher == :result
39
+ return value == true || value == false if matcher == :boolean
40
+
41
+ value.is_a?(matcher)
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Igniter
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/igniter.rb CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative "igniter/version"
4
4
  require_relative "igniter/errors"
5
+ require_relative "igniter/type_system"
6
+ require_relative "igniter/executor"
7
+ require_relative "igniter/executor_registry"
5
8
  require_relative "igniter/model"
6
9
  require_relative "igniter/compiler"
7
10
  require_relative "igniter/events"
@@ -13,8 +16,28 @@ require_relative "igniter/contract"
13
16
 
14
17
  module Igniter
15
18
  class << self
19
+ def executor_registry
20
+ @executor_registry ||= ExecutorRegistry.new
21
+ end
22
+
23
+ def execution_store
24
+ @execution_store ||= Runtime::Stores::MemoryStore.new
25
+ end
26
+
27
+ def execution_store=(store)
28
+ @execution_store = store
29
+ end
30
+
31
+ def register_executor(key, executor_class, **metadata)
32
+ executor_registry.register(key, executor_class, **metadata)
33
+ end
34
+
16
35
  def compile(&block)
17
36
  DSL::ContractBuilder.compile(&block)
18
37
  end
38
+
39
+ def compile_schema(schema, name: nil)
40
+ DSL::SchemaBuilder.compile(schema, name: name)
41
+ end
19
42
  end
20
43
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: igniter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander
@@ -51,37 +51,65 @@ files:
51
51
  - README.md
52
52
  - docs/API_V2.md
53
53
  - docs/ARCHITECTURE_V2.md
54
+ - docs/BACKLOG.md
55
+ - docs/BRANCHES_V1.md
56
+ - docs/COLLECTIONS_V1.md
54
57
  - docs/EXECUTION_MODEL_V2.md
55
58
  - docs/IGNITER_CONCEPTS.md
59
+ - docs/PATTERNS.md
60
+ - docs/STORE_ADAPTERS.md
56
61
  - examples/README.md
62
+ - examples/async_store.rb
57
63
  - examples/basic_pricing.rb
64
+ - examples/collection.rb
65
+ - examples/collection_partial_failure.rb
58
66
  - examples/composition.rb
59
67
  - examples/diagnostics.rb
68
+ - examples/marketing_ergonomics.rb
69
+ - examples/ringcentral_routing.rb
60
70
  - lib/igniter.rb
61
71
  - lib/igniter/compiler.rb
62
72
  - lib/igniter/compiler/compiled_graph.rb
63
73
  - lib/igniter/compiler/graph_compiler.rb
74
+ - lib/igniter/compiler/type_resolver.rb
75
+ - lib/igniter/compiler/validation_context.rb
76
+ - lib/igniter/compiler/validation_pipeline.rb
64
77
  - lib/igniter/compiler/validator.rb
78
+ - lib/igniter/compiler/validators/callable_validator.rb
79
+ - lib/igniter/compiler/validators/dependencies_validator.rb
80
+ - lib/igniter/compiler/validators/outputs_validator.rb
81
+ - lib/igniter/compiler/validators/type_compatibility_validator.rb
82
+ - lib/igniter/compiler/validators/uniqueness_validator.rb
65
83
  - lib/igniter/contract.rb
66
84
  - lib/igniter/diagnostics.rb
85
+ - lib/igniter/diagnostics/auditing/report/console_formatter.rb
86
+ - lib/igniter/diagnostics/auditing/report/markdown_formatter.rb
87
+ - lib/igniter/diagnostics/introspection/formatters/mermaid_formatter.rb
88
+ - lib/igniter/diagnostics/introspection/formatters/text_tree_formatter.rb
67
89
  - lib/igniter/diagnostics/report.rb
68
90
  - lib/igniter/dsl.rb
69
91
  - lib/igniter/dsl/contract_builder.rb
92
+ - lib/igniter/dsl/schema_builder.rb
70
93
  - lib/igniter/errors.rb
71
94
  - lib/igniter/events.rb
72
95
  - lib/igniter/events/bus.rb
73
96
  - lib/igniter/events/event.rb
97
+ - lib/igniter/executor.rb
98
+ - lib/igniter/executor_registry.rb
74
99
  - lib/igniter/extensions.rb
75
100
  - lib/igniter/extensions/auditing.rb
76
101
  - lib/igniter/extensions/auditing/timeline.rb
77
102
  - lib/igniter/extensions/introspection.rb
78
103
  - lib/igniter/extensions/introspection/graph_formatter.rb
104
+ - lib/igniter/extensions/introspection/plan_formatter.rb
79
105
  - lib/igniter/extensions/introspection/runtime_formatter.rb
80
106
  - lib/igniter/extensions/reactive.rb
81
107
  - lib/igniter/extensions/reactive/engine.rb
82
108
  - lib/igniter/extensions/reactive/matcher.rb
83
109
  - lib/igniter/extensions/reactive/reaction.rb
84
110
  - lib/igniter/model.rb
111
+ - lib/igniter/model/branch_node.rb
112
+ - lib/igniter/model/collection_node.rb
85
113
  - lib/igniter/model/composition_node.rb
86
114
  - lib/igniter/model/compute_node.rb
87
115
  - lib/igniter/model/graph.rb
@@ -90,12 +118,25 @@ files:
90
118
  - lib/igniter/model/output_node.rb
91
119
  - lib/igniter/runtime.rb
92
120
  - lib/igniter/runtime/cache.rb
121
+ - lib/igniter/runtime/collection_result.rb
122
+ - lib/igniter/runtime/deferred_result.rb
93
123
  - lib/igniter/runtime/execution.rb
94
124
  - lib/igniter/runtime/input_validator.rb
95
125
  - lib/igniter/runtime/invalidator.rb
126
+ - lib/igniter/runtime/job_worker.rb
96
127
  - lib/igniter/runtime/node_state.rb
128
+ - lib/igniter/runtime/planner.rb
97
129
  - lib/igniter/runtime/resolver.rb
98
130
  - lib/igniter/runtime/result.rb
131
+ - lib/igniter/runtime/runner_factory.rb
132
+ - lib/igniter/runtime/runners/inline_runner.rb
133
+ - lib/igniter/runtime/runners/store_runner.rb
134
+ - lib/igniter/runtime/runners/thread_pool_runner.rb
135
+ - lib/igniter/runtime/stores/active_record_store.rb
136
+ - lib/igniter/runtime/stores/file_store.rb
137
+ - lib/igniter/runtime/stores/memory_store.rb
138
+ - lib/igniter/runtime/stores/redis_store.rb
139
+ - lib/igniter/type_system.rb
99
140
  - lib/igniter/version.rb
100
141
  - sig/igniter.rbs
101
142
  homepage: https://github.com/alexander-s-f/igniter
@@ -120,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
161
  - !ruby/object:Gem::Version
121
162
  version: '0'
122
163
  requirements: []
123
- rubygems_version: 3.6.9
164
+ rubygems_version: 3.7.2
124
165
  specification_version: 4
125
166
  summary: Declarative dependency-graph runtime for business logic
126
167
  test_files: []