simple_flow 0.1.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 +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +4 -0
- data/COMMITS.md +196 -0
- data/LICENSE +21 -0
- data/README.md +481 -0
- data/Rakefile +15 -0
- data/benchmarks/parallel_vs_sequential.rb +98 -0
- data/benchmarks/pipeline_overhead.rb +130 -0
- data/docs/api/middleware.md +468 -0
- data/docs/api/parallel-step.md +363 -0
- data/docs/api/pipeline.md +382 -0
- data/docs/api/result.md +375 -0
- data/docs/concurrent/best-practices.md +687 -0
- data/docs/concurrent/introduction.md +246 -0
- data/docs/concurrent/parallel-steps.md +418 -0
- data/docs/concurrent/performance.md +481 -0
- data/docs/core-concepts/flow-control.md +452 -0
- data/docs/core-concepts/middleware.md +389 -0
- data/docs/core-concepts/overview.md +219 -0
- data/docs/core-concepts/pipeline.md +315 -0
- data/docs/core-concepts/result.md +168 -0
- data/docs/core-concepts/steps.md +391 -0
- data/docs/development/benchmarking.md +443 -0
- data/docs/development/contributing.md +380 -0
- data/docs/development/dagwood-concepts.md +435 -0
- data/docs/development/testing.md +514 -0
- data/docs/getting-started/examples.md +197 -0
- data/docs/getting-started/installation.md +62 -0
- data/docs/getting-started/quick-start.md +218 -0
- data/docs/guides/choosing-concurrency-model.md +441 -0
- data/docs/guides/complex-workflows.md +440 -0
- data/docs/guides/data-fetching.md +478 -0
- data/docs/guides/error-handling.md +635 -0
- data/docs/guides/file-processing.md +505 -0
- data/docs/guides/validation-patterns.md +496 -0
- data/docs/index.md +169 -0
- data/examples/.gitignore +3 -0
- data/examples/01_basic_pipeline.rb +112 -0
- data/examples/02_error_handling.rb +178 -0
- data/examples/03_middleware.rb +186 -0
- data/examples/04_parallel_automatic.rb +221 -0
- data/examples/05_parallel_explicit.rb +279 -0
- data/examples/06_real_world_ecommerce.rb +288 -0
- data/examples/07_real_world_etl.rb +277 -0
- data/examples/08_graph_visualization.rb +246 -0
- data/examples/09_pipeline_visualization.rb +266 -0
- data/examples/10_concurrency_control.rb +235 -0
- data/examples/11_sequential_dependencies.rb +243 -0
- data/examples/12_none_constant.rb +161 -0
- data/examples/README.md +374 -0
- data/examples/regression_test/01_basic_pipeline.txt +38 -0
- data/examples/regression_test/02_error_handling.txt +92 -0
- data/examples/regression_test/03_middleware.txt +61 -0
- data/examples/regression_test/04_parallel_automatic.txt +86 -0
- data/examples/regression_test/05_parallel_explicit.txt +80 -0
- data/examples/regression_test/06_real_world_ecommerce.txt +53 -0
- data/examples/regression_test/07_real_world_etl.txt +58 -0
- data/examples/regression_test/08_graph_visualization.txt +429 -0
- data/examples/regression_test/09_pipeline_visualization.txt +305 -0
- data/examples/regression_test/10_concurrency_control.txt +96 -0
- data/examples/regression_test/11_sequential_dependencies.txt +86 -0
- data/examples/regression_test/12_none_constant.txt +64 -0
- data/examples/regression_test.rb +105 -0
- data/lib/simple_flow/dependency_graph.rb +120 -0
- data/lib/simple_flow/dependency_graph_visualizer.rb +326 -0
- data/lib/simple_flow/middleware.rb +36 -0
- data/lib/simple_flow/parallel_executor.rb +80 -0
- data/lib/simple_flow/pipeline.rb +405 -0
- data/lib/simple_flow/result.rb +88 -0
- data/lib/simple_flow/step_tracker.rb +58 -0
- data/lib/simple_flow/version.rb +5 -0
- data/lib/simple_flow.rb +41 -0
- data/mkdocs.yml +146 -0
- data/pipeline_graph.dot +51 -0
- data/pipeline_graph.html +60 -0
- data/pipeline_graph.mmd +19 -0
- metadata +127 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# Pipeline API Reference
|
|
2
|
+
|
|
3
|
+
The `Pipeline` class orchestrates step execution with middleware integration and parallel execution support.
|
|
4
|
+
|
|
5
|
+
## Class: `SimpleFlow::Pipeline`
|
|
6
|
+
|
|
7
|
+
**Location**: `/Users/dewayne/sandbox/git_repos/madbomber/simple_flow/lib/simple_flow/pipeline.rb`
|
|
8
|
+
|
|
9
|
+
### Constructor
|
|
10
|
+
|
|
11
|
+
#### `new(&config)`
|
|
12
|
+
|
|
13
|
+
Creates a new Pipeline with optional configuration block.
|
|
14
|
+
|
|
15
|
+
**Parameters:**
|
|
16
|
+
- `config` (Block, optional) - Configuration block for defining steps and middleware
|
|
17
|
+
|
|
18
|
+
**Example:**
|
|
19
|
+
```ruby
|
|
20
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
21
|
+
use_middleware SimpleFlow::MiddleWare::Logging
|
|
22
|
+
step ->(result) { result.continue(result.value + 1) }
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### DSL Methods
|
|
27
|
+
|
|
28
|
+
#### `use_middleware(middleware, options = {})`
|
|
29
|
+
|
|
30
|
+
Registers middleware to be applied to each step.
|
|
31
|
+
|
|
32
|
+
**Parameters:**
|
|
33
|
+
- `middleware` (Class/Proc) - Middleware class or proc
|
|
34
|
+
- `options` (Hash) - Options passed to middleware constructor
|
|
35
|
+
|
|
36
|
+
**Example:**
|
|
37
|
+
```ruby
|
|
38
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
39
|
+
use_middleware SimpleFlow::MiddleWare::Logging
|
|
40
|
+
use_middleware SimpleFlow::MiddleWare::Instrumentation, api_key: 'xyz'
|
|
41
|
+
use_middleware CustomMiddleware, timeout: 30
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### `step(name_or_callable = nil, callable = nil, depends_on: [], &block)`
|
|
46
|
+
|
|
47
|
+
Adds a step to the pipeline. Supports named and unnamed steps.
|
|
48
|
+
|
|
49
|
+
**Parameters:**
|
|
50
|
+
- `name_or_callable` (Symbol/Proc/Object) - Step name or callable
|
|
51
|
+
- `callable` (Proc/Object) - Callable object (if first param is name)
|
|
52
|
+
- `depends_on` (Array) - Dependencies for named steps
|
|
53
|
+
- `block` (Block) - Block to use as step
|
|
54
|
+
|
|
55
|
+
**Returns:** self (for chaining)
|
|
56
|
+
|
|
57
|
+
**Named Steps:**
|
|
58
|
+
```ruby
|
|
59
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
60
|
+
step :fetch_user, ->(result) { ... }, depends_on: []
|
|
61
|
+
step :process_data, ->(result) { ... }, depends_on: [:fetch_user]
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Unnamed Steps:**
|
|
66
|
+
```ruby
|
|
67
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
68
|
+
step ->(result) { result.continue(result.value + 1) }
|
|
69
|
+
step { |result| result.continue(result.value * 2) }
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Class-Based Steps:**
|
|
74
|
+
```ruby
|
|
75
|
+
class FetchUser
|
|
76
|
+
def call(result)
|
|
77
|
+
user = User.find(result.value)
|
|
78
|
+
result.with_context(:user, user).continue(result.value)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
83
|
+
step :fetch_user, FetchUser.new, depends_on: []
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### `parallel(&block)`
|
|
88
|
+
|
|
89
|
+
Defines an explicit parallel execution block.
|
|
90
|
+
|
|
91
|
+
**Parameters:**
|
|
92
|
+
- `block` (Block) - Block containing step definitions
|
|
93
|
+
|
|
94
|
+
**Returns:** self (for chaining)
|
|
95
|
+
|
|
96
|
+
**Example:**
|
|
97
|
+
```ruby
|
|
98
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
99
|
+
step ->(result) { result.continue(validate(result.value)) }
|
|
100
|
+
|
|
101
|
+
parallel do
|
|
102
|
+
step ->(result) { result.with_context(:api, fetch_api).continue(result.value) }
|
|
103
|
+
step ->(result) { result.with_context(:db, fetch_db).continue(result.value) }
|
|
104
|
+
step ->(result) { result.with_context(:cache, fetch_cache).continue(result.value) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
step ->(result) { result.continue(merge_data(result.context)) }
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Execution Methods
|
|
112
|
+
|
|
113
|
+
#### `call(result)`
|
|
114
|
+
|
|
115
|
+
Executes the pipeline sequentially with a given initial result.
|
|
116
|
+
|
|
117
|
+
**Parameters:**
|
|
118
|
+
- `result` (Result) - Initial Result object
|
|
119
|
+
|
|
120
|
+
**Returns:** Final Result object
|
|
121
|
+
|
|
122
|
+
**Example:**
|
|
123
|
+
```ruby
|
|
124
|
+
initial = SimpleFlow::Result.new(5)
|
|
125
|
+
result = pipeline.call(initial)
|
|
126
|
+
|
|
127
|
+
result.value # => Final value
|
|
128
|
+
result.context # => Accumulated context
|
|
129
|
+
result.errors # => Any errors
|
|
130
|
+
result.continue? # => true/false
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `call_parallel(result, strategy: :auto)`
|
|
134
|
+
|
|
135
|
+
Executes the pipeline with parallel execution where possible.
|
|
136
|
+
|
|
137
|
+
**Parameters:**
|
|
138
|
+
- `result` (Result) - Initial Result object
|
|
139
|
+
- `strategy` (Symbol) - Parallelization strategy (`:auto` or `:explicit`)
|
|
140
|
+
|
|
141
|
+
**Returns:** Final Result object
|
|
142
|
+
|
|
143
|
+
**Strategies:**
|
|
144
|
+
- `:auto` (default) - Uses dependency graph if named steps exist
|
|
145
|
+
- `:explicit` - Only uses explicit parallel blocks
|
|
146
|
+
|
|
147
|
+
**Example:**
|
|
148
|
+
```ruby
|
|
149
|
+
# Automatic strategy (uses dependency graph)
|
|
150
|
+
result = pipeline.call_parallel(initial_data)
|
|
151
|
+
|
|
152
|
+
# Explicit strategy
|
|
153
|
+
result = pipeline.call_parallel(initial_data, strategy: :explicit)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Visualization Methods
|
|
157
|
+
|
|
158
|
+
#### `visualize_ascii(show_groups: true)`
|
|
159
|
+
|
|
160
|
+
Returns ASCII visualization of the dependency graph.
|
|
161
|
+
|
|
162
|
+
**Parameters:**
|
|
163
|
+
- `show_groups` (Boolean) - Whether to show parallel execution groups (default: true)
|
|
164
|
+
|
|
165
|
+
**Returns:** String (ASCII art) or nil if no named steps
|
|
166
|
+
|
|
167
|
+
**Example:**
|
|
168
|
+
```ruby
|
|
169
|
+
puts pipeline.visualize_ascii
|
|
170
|
+
|
|
171
|
+
# Hide parallel groups
|
|
172
|
+
puts pipeline.visualize_ascii(show_groups: false)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### `visualize_dot(include_groups: true, orientation: 'TB')`
|
|
176
|
+
|
|
177
|
+
Exports dependency graph to Graphviz DOT format.
|
|
178
|
+
|
|
179
|
+
**Parameters:**
|
|
180
|
+
- `include_groups` (Boolean) - Color-code parallel groups (default: true)
|
|
181
|
+
- `orientation` (String) - Graph orientation: 'TB' (top-bottom) or 'LR' (left-right)
|
|
182
|
+
|
|
183
|
+
**Returns:** String (DOT format) or nil if no named steps
|
|
184
|
+
|
|
185
|
+
**Example:**
|
|
186
|
+
```ruby
|
|
187
|
+
File.write('pipeline.dot', pipeline.visualize_dot)
|
|
188
|
+
# Generate image: dot -Tpng pipeline.dot -o pipeline.png
|
|
189
|
+
|
|
190
|
+
# Left-to-right layout
|
|
191
|
+
File.write('pipeline.dot', pipeline.visualize_dot(orientation: 'LR'))
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### `visualize_mermaid()`
|
|
195
|
+
|
|
196
|
+
Exports dependency graph to Mermaid diagram format.
|
|
197
|
+
|
|
198
|
+
**Returns:** String (Mermaid format) or nil if no named steps
|
|
199
|
+
|
|
200
|
+
**Example:**
|
|
201
|
+
```ruby
|
|
202
|
+
File.write('pipeline.mmd', pipeline.visualize_mermaid)
|
|
203
|
+
# View at https://mermaid.live/
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### `execution_plan()`
|
|
207
|
+
|
|
208
|
+
Returns detailed execution plan analysis.
|
|
209
|
+
|
|
210
|
+
**Returns:** String (execution plan) or nil if no named steps
|
|
211
|
+
|
|
212
|
+
**Example:**
|
|
213
|
+
```ruby
|
|
214
|
+
puts pipeline.execution_plan
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Output includes:
|
|
218
|
+
- Total steps and execution phases
|
|
219
|
+
- Which steps run in parallel
|
|
220
|
+
- Potential speedup vs sequential execution
|
|
221
|
+
|
|
222
|
+
### Utility Methods
|
|
223
|
+
|
|
224
|
+
#### `async_available?`
|
|
225
|
+
|
|
226
|
+
Checks if the async gem is available for true parallel execution.
|
|
227
|
+
|
|
228
|
+
**Returns:** Boolean
|
|
229
|
+
|
|
230
|
+
**Example:**
|
|
231
|
+
```ruby
|
|
232
|
+
if pipeline.async_available?
|
|
233
|
+
puts "Parallel execution enabled"
|
|
234
|
+
else
|
|
235
|
+
puts "Falling back to sequential execution"
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `dependency_graph`
|
|
240
|
+
|
|
241
|
+
Returns the dependency graph for this pipeline.
|
|
242
|
+
|
|
243
|
+
**Returns:** DependencyGraph or nil if no named steps
|
|
244
|
+
|
|
245
|
+
**Example:**
|
|
246
|
+
```ruby
|
|
247
|
+
graph = pipeline.dependency_graph
|
|
248
|
+
if graph
|
|
249
|
+
puts graph.order # => [:step1, :step2, :step3]
|
|
250
|
+
puts graph.parallel_order # => [[:step1], [:step2, :step3]]
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `visualize`
|
|
255
|
+
|
|
256
|
+
Creates a visualizer for this pipeline's dependency graph.
|
|
257
|
+
|
|
258
|
+
**Returns:** DependencyGraphVisualizer or nil if no named steps
|
|
259
|
+
|
|
260
|
+
**Example:**
|
|
261
|
+
```ruby
|
|
262
|
+
visualizer = pipeline.visualize
|
|
263
|
+
if visualizer
|
|
264
|
+
puts visualizer.to_ascii
|
|
265
|
+
File.write('graph.dot', visualizer.to_dot)
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Instance Attributes
|
|
270
|
+
|
|
271
|
+
#### `steps`
|
|
272
|
+
|
|
273
|
+
Array of step definitions (read-only).
|
|
274
|
+
|
|
275
|
+
**Type:** Array
|
|
276
|
+
|
|
277
|
+
#### `middlewares`
|
|
278
|
+
|
|
279
|
+
Array of registered middleware (read-only).
|
|
280
|
+
|
|
281
|
+
**Type:** Array
|
|
282
|
+
|
|
283
|
+
#### `named_steps`
|
|
284
|
+
|
|
285
|
+
Hash of named steps (read-only).
|
|
286
|
+
|
|
287
|
+
**Type:** Hash
|
|
288
|
+
|
|
289
|
+
#### `step_dependencies`
|
|
290
|
+
|
|
291
|
+
Hash of step dependencies (read-only).
|
|
292
|
+
|
|
293
|
+
**Type:** Hash
|
|
294
|
+
|
|
295
|
+
## Usage Examples
|
|
296
|
+
|
|
297
|
+
### Basic Sequential Pipeline
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
301
|
+
step ->(result) { result.continue(result.value.strip) }
|
|
302
|
+
step ->(result) { result.continue(result.value.downcase) }
|
|
303
|
+
step ->(result) { result.continue("Hello, #{result.value}!") }
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
result = pipeline.call(SimpleFlow::Result.new(" WORLD "))
|
|
307
|
+
result.value # => "Hello, world!"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Parallel Pipeline with Dependencies
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
314
|
+
step :fetch_user, ->(result) {
|
|
315
|
+
user = User.find(result.value)
|
|
316
|
+
result.with_context(:user, user).continue(result.value)
|
|
317
|
+
}, depends_on: []
|
|
318
|
+
|
|
319
|
+
step :fetch_orders, ->(result) {
|
|
320
|
+
orders = Order.where(user_id: result.context[:user].id)
|
|
321
|
+
result.with_context(:orders, orders).continue(result.value)
|
|
322
|
+
}, depends_on: [:fetch_user]
|
|
323
|
+
|
|
324
|
+
step :fetch_preferences, ->(result) {
|
|
325
|
+
prefs = Preference.where(user_id: result.context[:user].id)
|
|
326
|
+
result.with_context(:preferences, prefs).continue(result.value)
|
|
327
|
+
}, depends_on: [:fetch_user]
|
|
328
|
+
|
|
329
|
+
step :build_profile, ->(result) {
|
|
330
|
+
profile = {
|
|
331
|
+
user: result.context[:user],
|
|
332
|
+
orders: result.context[:orders],
|
|
333
|
+
preferences: result.context[:preferences]
|
|
334
|
+
}
|
|
335
|
+
result.continue(profile)
|
|
336
|
+
}, depends_on: [:fetch_orders, :fetch_preferences]
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# fetch_orders and fetch_preferences run in parallel
|
|
340
|
+
result = pipeline.call_parallel(SimpleFlow::Result.new(user_id))
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Pipeline with Middleware
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
347
|
+
use_middleware SimpleFlow::MiddleWare::Logging
|
|
348
|
+
use_middleware SimpleFlow::MiddleWare::Instrumentation, api_key: 'demo'
|
|
349
|
+
|
|
350
|
+
step ->(result) { result.continue(process(result.value)) }
|
|
351
|
+
end
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Mixed Execution Styles
|
|
355
|
+
|
|
356
|
+
```ruby
|
|
357
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
358
|
+
# Unnamed sequential step
|
|
359
|
+
step ->(result) { result.continue(sanitize(result.value)) }
|
|
360
|
+
|
|
361
|
+
# Named steps with automatic parallelism
|
|
362
|
+
step :step_a, ->(result) { ... }, depends_on: []
|
|
363
|
+
step :step_b, ->(result) { ... }, depends_on: []
|
|
364
|
+
step :step_c, ->(result) { ... }, depends_on: [:step_a, :step_b]
|
|
365
|
+
|
|
366
|
+
# Explicit parallel block
|
|
367
|
+
parallel do
|
|
368
|
+
step ->(result) { ... }
|
|
369
|
+
step ->(result) { ... }
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Another sequential step
|
|
373
|
+
step ->(result) { result.continue(finalize(result.value)) }
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Related Documentation
|
|
378
|
+
|
|
379
|
+
- [Result API](result.md) - Result class reference
|
|
380
|
+
- [Parallel Steps Guide](../concurrent/parallel-steps.md) - Using named steps
|
|
381
|
+
- [Middleware API](middleware.md) - Middleware reference
|
|
382
|
+
- [Performance Guide](../concurrent/performance.md) - Optimization strategies
|