ask-graph 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3062914e848e9fea15dd9b02bd001946b7a3fd0ab015a1805b4e21a371b68bf
4
- data.tar.gz: 17866f6bfaf40d6e195c71655ac134430747e3690ce32c355036a8e30a62bbf1
3
+ metadata.gz: 5bcdf4454ef1639db46defba3c2b59377dd0aa719036b27be25b77a7a805fdec
4
+ data.tar.gz: eef23d886d551984bbd8f1514e7bdfacc38be0d4b029ec832b1e7c61f2d2036f
5
5
  SHA512:
6
- metadata.gz: ac9dad679a58246c1bd04d67de6812e9126f8f341edad4a72e842b80b759bcf2e020a2ad0c5bedd625eff861f22f0cbb224d4d2dc058a136d4c13087c4420248
7
- data.tar.gz: b0f7285fd1e447c3f48e06d061aa441f64bb37de1952022e22e44835c3b549c639bceec13d9a690d0d14becb5eb724e1d2f31c05ea47a514ba2be57d9680de3b
6
+ metadata.gz: bef581ebc5816f79ab0b8d7da98dd9cf53fba824e1e68691e5b39ef8ee0eca3380fa2994dde965ad17db24267f9541b4c537c87fa26a3852ba9fa128afc60237
7
+ data.tar.gz: 78d63a0b802ee4050d09a08ab4d6f62a7fa8e71425745ef055d2f02824c6fb92f48c3149fadb275723aefb93b04bf0a04c258246b115c4ad028b5a56179a9188
data/CHANGELOG.md CHANGED
@@ -1,3 +1,57 @@
1
+ ## [0.5.0] — 2026-07-27
2
+
3
+ ### Added
4
+
5
+ - **Sub-graph composition** — use any `Ask::Graph` subclass as a step in another
6
+ graph. The sub-graph runs all its internal steps and merges its context back
7
+ into the outer graph.
8
+
9
+ ```ruby
10
+ class NotifyCustomer < Ask::Graph
11
+ step SendEmail
12
+ step LogNotification
13
+ end
14
+
15
+ class HandleOrder < Ask::Graph
16
+ step ValidatePayment
17
+ step NotifyCustomer # runs the sub-graph, merges its context
18
+ step ShipOrder
19
+ end
20
+ ```
21
+
22
+ - Sub-graphs read the outer graph's context directly — no special wiring
23
+ - Sub-graph writes are merged back automatically
24
+ - Supports nesting (sub-graphs within sub-graphs)
25
+ - Supports `timeout:`, `retry:`, and `if:`/`unless:` on the outer step
26
+ - Supports parallel steps inside sub-graphs
27
+ - Shares the same `storage` backend with the outer graph
28
+
29
+ - **`Context#export_data`** — returns the raw store hash without JSON
30
+ serialization. Used internally for sub-graph data passing.
31
+
32
+ - **`Context#import(other)`** — merges data from another context or hash
33
+ into this one. Used internally by sub-graph composition.
34
+
35
+ - **Hash inputs now flatten into context keys** — when a Hash is passed as
36
+ input to a graph, its keys are directly accessible on the context in
37
+ addition to `context.input`. This enables sub-graphs to transparently
38
+ read outer context values.
39
+
40
+ ```ruby
41
+ g.new({ value: 42 }).call
42
+ ctx.value # => 42 (new — directly accessible)
43
+ ctx.input # => { value: 42 } (still works)
44
+ ```
45
+
46
+ ### Limitations
47
+
48
+ - Sub-graphs containing `approve` steps are not yet supported and raise
49
+ `ArgumentError`. Nested approval will be added in a future release.
50
+
51
+ ### Tested
52
+
53
+ - 87 tests, 114 assertions, 0 failures
54
+
1
55
  ## [0.4.0] — 2026-07-27
2
56
 
3
57
  ### Changed
@@ -31,7 +31,12 @@ module Ask
31
31
  @item = nil
32
32
  @mutex = Mutex.new
33
33
  @resume_input = nil
34
- @store[:input] = input if input
34
+ if input.is_a?(Hash)
35
+ @store[:input] = input
36
+ @store.merge!(input)
37
+ elsif input
38
+ @store[:input] = input
39
+ end
35
40
  end
36
41
 
37
42
  # @return [Object, nil] input provided on resume for approval steps
@@ -102,6 +107,22 @@ module Ask
102
107
  end
103
108
  end
104
109
 
110
+ # @return [Hash] raw store data with original object references.
111
+ # Unlike {to_h}, this returns the actual Ruby objects without
112
+ # serialization. Useful for passing state to sub-graphs.
113
+ def export_data
114
+ @mutex.synchronize { @store.dup }
115
+ end
116
+
117
+ # Merge data from another context (or hash) into this one.
118
+ # Existing keys in this context are overwritten.
119
+ #
120
+ # @param other [Context, Hash] source of data to merge in
121
+ def import(other)
122
+ data = other.is_a?(Context) ? other.export_data : other
123
+ @mutex.synchronize { @store.merge!(data) }
124
+ end
125
+
105
126
  private
106
127
 
107
128
  # Recursively convert a value to a JSON-safe structure.
@@ -133,14 +133,29 @@ module Ask
133
133
  end
134
134
 
135
135
  def run_single(klass, _name, context)
136
- instance = klass.new
137
- instance.call(context)
138
- rescue Paused
136
+ if klass < Ask::Graph
137
+ run_subgraph(klass, context)
138
+ else
139
+ instance = klass.new
140
+ instance.call(context)
141
+ end
142
+ rescue Paused, ArgumentError
139
143
  raise
140
144
  rescue => e
141
145
  raise StepFailed, "#{klass.name} failed: #{e.message}"
142
146
  end
143
147
 
148
+ def run_subgraph(klass, context)
149
+ if klass.respond_to?(:declarations) && klass.declarations.any? { |d| d[:type] == :approve }
150
+ raise ArgumentError,
151
+ "#{klass.name} contains approve steps — sub-graphs with approve are not yet supported"
152
+ end
153
+
154
+ sub = klass.new(context.export_data, storage: @store)
155
+ sub_ctx = sub.call
156
+ context.import(sub_ctx) if sub_ctx
157
+ end
158
+
144
159
  def run_parallel(classes, _name, context)
145
160
  threads = classes.map do |klass|
146
161
  Thread.new do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  class Graph
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
data/lib/ask/graph.rb CHANGED
@@ -6,8 +6,9 @@ require_relative "graph/runner"
6
6
 
7
7
  module Ask
8
8
  # Define durable workflow graphs with named steps, conditional routing,
9
- # parallel execution, human-in-the-loop approval, per-item checkpointing
10
- # loops, step timeouts, retry policies, and lifecycle hooks.
9
+ # parallel execution, sub-graph composition, human-in-the-loop approval,
10
+ # per-item checkpointing loops, step timeouts, retry policies, and
11
+ # lifecycle hooks.
11
12
  #
12
13
  # @example Basic workflow
13
14
  # class HandleCall < Ask::Graph
@@ -16,6 +17,18 @@ module Ask
16
17
  # step BookAppointment
17
18
  # end
18
19
  #
20
+ # @example Sub-graph composition (reuse a graph as a step)
21
+ # class NotifyCustomer < Ask::Graph
22
+ # step SendEmail
23
+ # step LogNotification
24
+ # end
25
+ #
26
+ # class HandleOrder < Ask::Graph
27
+ # step ValidatePayment
28
+ # step NotifyCustomer # runs the sub-graph, merges its context
29
+ # step ShipOrder
30
+ # end
31
+ #
19
32
  # @example With timeout and retry
20
33
  # class ApiWorkflow < Ask::Graph
21
34
  # step FetchData, timeout: 30, retry: 3
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto