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
data/docs/api/result.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# Result API Reference
|
|
2
|
+
|
|
3
|
+
The `Result` class is an immutable value object that represents the outcome of a step in a SimpleFlow pipeline. It encapsulates the operation's value, contextual data, and any errors that occurred.
|
|
4
|
+
|
|
5
|
+
## Class: `SimpleFlow::Result`
|
|
6
|
+
|
|
7
|
+
**Location**: `/Users/dewayne/sandbox/git_repos/madbomber/simple_flow/lib/simple_flow/result.rb`
|
|
8
|
+
|
|
9
|
+
### Constructor
|
|
10
|
+
|
|
11
|
+
#### `new(value, context: {}, errors: {})`
|
|
12
|
+
|
|
13
|
+
Creates a new Result instance.
|
|
14
|
+
|
|
15
|
+
**Parameters:**
|
|
16
|
+
- `value` (Object) - The outcome of the operation
|
|
17
|
+
- `context` (Hash, optional) - Contextual data related to the operation (default: `{}`)
|
|
18
|
+
- `errors` (Hash, optional) - Errors organized by category (default: `{}`)
|
|
19
|
+
|
|
20
|
+
**Returns:** Result instance
|
|
21
|
+
|
|
22
|
+
**Example:**
|
|
23
|
+
```ruby
|
|
24
|
+
# Basic result
|
|
25
|
+
result = SimpleFlow::Result.new(42)
|
|
26
|
+
|
|
27
|
+
# Result with context
|
|
28
|
+
result = SimpleFlow::Result.new(
|
|
29
|
+
{ user_id: 123 },
|
|
30
|
+
context: { timestamp: Time.now }
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Result with errors
|
|
34
|
+
result = SimpleFlow::Result.new(
|
|
35
|
+
nil,
|
|
36
|
+
errors: { validation: ["Email is required"] }
|
|
37
|
+
)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Instance Attributes
|
|
41
|
+
|
|
42
|
+
#### `value`
|
|
43
|
+
|
|
44
|
+
The outcome of the operation.
|
|
45
|
+
|
|
46
|
+
**Type:** Object (read-only)
|
|
47
|
+
|
|
48
|
+
**Example:**
|
|
49
|
+
```ruby
|
|
50
|
+
result = SimpleFlow::Result.new(42)
|
|
51
|
+
result.value # => 42
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### `context`
|
|
55
|
+
|
|
56
|
+
Contextual data related to the operation.
|
|
57
|
+
|
|
58
|
+
**Type:** Hash (read-only)
|
|
59
|
+
|
|
60
|
+
**Example:**
|
|
61
|
+
```ruby
|
|
62
|
+
result = SimpleFlow::Result.new(42, context: { user: "John" })
|
|
63
|
+
result.context # => { user: "John" }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### `errors`
|
|
67
|
+
|
|
68
|
+
Errors that occurred during the operation, organized by category.
|
|
69
|
+
|
|
70
|
+
**Type:** Hash (read-only)
|
|
71
|
+
|
|
72
|
+
**Example:**
|
|
73
|
+
```ruby
|
|
74
|
+
result = SimpleFlow::Result.new(nil, errors: {
|
|
75
|
+
validation: ["Email required", "Password too short"],
|
|
76
|
+
auth: ["Invalid credentials"]
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
result.errors[:validation] # => ["Email required", "Password too short"]
|
|
80
|
+
result.errors[:auth] # => ["Invalid credentials"]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Instance Methods
|
|
84
|
+
|
|
85
|
+
#### `with_context(key, value)`
|
|
86
|
+
|
|
87
|
+
Adds or updates context to the result. Returns a new Result instance with updated context.
|
|
88
|
+
|
|
89
|
+
**Parameters:**
|
|
90
|
+
- `key` (Symbol) - The key to store the context under
|
|
91
|
+
- `value` (Object) - The value to store
|
|
92
|
+
|
|
93
|
+
**Returns:** New Result instance
|
|
94
|
+
|
|
95
|
+
**Immutability:** This method creates a new Result object; the original is unchanged.
|
|
96
|
+
|
|
97
|
+
**Example:**
|
|
98
|
+
```ruby
|
|
99
|
+
result = SimpleFlow::Result.new(42)
|
|
100
|
+
.with_context(:user_id, 123)
|
|
101
|
+
.with_context(:timestamp, Time.now)
|
|
102
|
+
|
|
103
|
+
result.context # => { user_id: 123, timestamp: 2025-11-15 12:00:00 }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Chaining:**
|
|
107
|
+
```ruby
|
|
108
|
+
result = SimpleFlow::Result.new(data)
|
|
109
|
+
.with_context(:step_name, "process_data")
|
|
110
|
+
.with_context(:duration, 0.5)
|
|
111
|
+
.with_context(:source, :api)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### `with_error(key, message)`
|
|
115
|
+
|
|
116
|
+
Adds an error message under a specific key. If the key already exists, the message is appended to existing errors. Returns a new Result instance with updated errors.
|
|
117
|
+
|
|
118
|
+
**Parameters:**
|
|
119
|
+
- `key` (Symbol) - The category under which to store the error
|
|
120
|
+
- `message` (String) - The error message
|
|
121
|
+
|
|
122
|
+
**Returns:** New Result instance
|
|
123
|
+
|
|
124
|
+
**Immutability:** Creates a new Result object.
|
|
125
|
+
|
|
126
|
+
**Example:**
|
|
127
|
+
```ruby
|
|
128
|
+
result = SimpleFlow::Result.new(nil)
|
|
129
|
+
.with_error(:validation, "Email is required")
|
|
130
|
+
.with_error(:validation, "Password too short")
|
|
131
|
+
.with_error(:auth, "Invalid credentials")
|
|
132
|
+
|
|
133
|
+
result.errors
|
|
134
|
+
# => {
|
|
135
|
+
# validation: ["Email is required", "Password too short"],
|
|
136
|
+
# auth: ["Invalid credentials"]
|
|
137
|
+
# }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Error Accumulation:**
|
|
141
|
+
```ruby
|
|
142
|
+
result = SimpleFlow::Result.new(data)
|
|
143
|
+
|
|
144
|
+
# Add first validation error
|
|
145
|
+
result = result.with_error(:validation, "Name is required")
|
|
146
|
+
|
|
147
|
+
# Add second validation error (accumulates)
|
|
148
|
+
result = result.with_error(:validation, "Email is required")
|
|
149
|
+
|
|
150
|
+
result.errors[:validation]
|
|
151
|
+
# => ["Name is required", "Email is required"]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### `halt(new_value = nil)`
|
|
155
|
+
|
|
156
|
+
Halts the pipeline flow. Optionally updates the result's value. Returns a new Result instance with `continue` set to false.
|
|
157
|
+
|
|
158
|
+
**Parameters:**
|
|
159
|
+
- `new_value` (Object, optional) - New value to set (default: keep current value)
|
|
160
|
+
|
|
161
|
+
**Returns:** New Result instance with `@continue = false`
|
|
162
|
+
|
|
163
|
+
**Example:**
|
|
164
|
+
```ruby
|
|
165
|
+
# Halt without changing value
|
|
166
|
+
result = SimpleFlow::Result.new(42).halt
|
|
167
|
+
result.continue? # => false
|
|
168
|
+
result.value # => 42
|
|
169
|
+
|
|
170
|
+
# Halt with new value
|
|
171
|
+
result = SimpleFlow::Result.new(42).halt(100)
|
|
172
|
+
result.continue? # => false
|
|
173
|
+
result.value # => 100
|
|
174
|
+
|
|
175
|
+
# Halt with error
|
|
176
|
+
result = SimpleFlow::Result.new(data)
|
|
177
|
+
.halt
|
|
178
|
+
.with_error(:validation, "Invalid input")
|
|
179
|
+
|
|
180
|
+
result.continue? # => false
|
|
181
|
+
result.errors # => { validation: ["Invalid input"] }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Usage in Steps:**
|
|
185
|
+
```ruby
|
|
186
|
+
step ->(result) {
|
|
187
|
+
if invalid?(result.value)
|
|
188
|
+
result.halt.with_error(:validation, "Invalid data")
|
|
189
|
+
else
|
|
190
|
+
result.continue(process(result.value))
|
|
191
|
+
end
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### `continue(new_value)`
|
|
196
|
+
|
|
197
|
+
Continues the pipeline flow with an updated value. Returns a new Result instance with the new value.
|
|
198
|
+
|
|
199
|
+
**Parameters:**
|
|
200
|
+
- `new_value` (Object) - The new value to set
|
|
201
|
+
|
|
202
|
+
**Returns:** New Result instance with updated value
|
|
203
|
+
|
|
204
|
+
**Example:**
|
|
205
|
+
```ruby
|
|
206
|
+
result = SimpleFlow::Result.new(5)
|
|
207
|
+
.continue(10)
|
|
208
|
+
|
|
209
|
+
result.continue? # => true
|
|
210
|
+
result.value # => 10
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Usage in Steps:**
|
|
214
|
+
```ruby
|
|
215
|
+
step ->(result) {
|
|
216
|
+
transformed = transform(result.value)
|
|
217
|
+
result.continue(transformed)
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### `continue?`
|
|
222
|
+
|
|
223
|
+
Checks if the pipeline should continue executing.
|
|
224
|
+
|
|
225
|
+
**Returns:** Boolean
|
|
226
|
+
- `true` if the pipeline should continue
|
|
227
|
+
- `false` if the pipeline has been halted
|
|
228
|
+
|
|
229
|
+
**Example:**
|
|
230
|
+
```ruby
|
|
231
|
+
result = SimpleFlow::Result.new(42)
|
|
232
|
+
result.continue? # => true
|
|
233
|
+
|
|
234
|
+
result = result.halt
|
|
235
|
+
result.continue? # => false
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Usage:**
|
|
239
|
+
```ruby
|
|
240
|
+
result = pipeline.call(initial_data)
|
|
241
|
+
|
|
242
|
+
if result.continue?
|
|
243
|
+
puts "Success: #{result.value}"
|
|
244
|
+
process_result(result)
|
|
245
|
+
else
|
|
246
|
+
puts "Failed: #{result.errors}"
|
|
247
|
+
handle_errors(result)
|
|
248
|
+
end
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Usage Patterns
|
|
252
|
+
|
|
253
|
+
### Basic Flow Control
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
step ->(result) {
|
|
257
|
+
if valid?(result.value)
|
|
258
|
+
result.continue(result.value)
|
|
259
|
+
else
|
|
260
|
+
result.halt.with_error(:validation, "Invalid")
|
|
261
|
+
end
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Error Accumulation
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
step ->(result) {
|
|
269
|
+
result_obj = result
|
|
270
|
+
|
|
271
|
+
if invalid_email?(result.value[:email])
|
|
272
|
+
result_obj = result_obj.with_error(:email, "Invalid format")
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
if invalid_phone?(result.value[:phone])
|
|
276
|
+
result_obj = result_obj.with_error(:phone, "Invalid format")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Continue even with errors (check later)
|
|
280
|
+
result_obj.continue(result.value)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
step ->(result) {
|
|
284
|
+
if result.errors.any?
|
|
285
|
+
result.halt(result.value)
|
|
286
|
+
else
|
|
287
|
+
result.continue(result.value)
|
|
288
|
+
end
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Context Propagation
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
296
|
+
step ->(result) {
|
|
297
|
+
result
|
|
298
|
+
.with_context(:started_at, Time.now)
|
|
299
|
+
.with_context(:user_id, 123)
|
|
300
|
+
.continue(result.value)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
step ->(result) {
|
|
304
|
+
# Access context from previous step
|
|
305
|
+
user_id = result.context[:user_id]
|
|
306
|
+
data = fetch_user_data(user_id)
|
|
307
|
+
|
|
308
|
+
result
|
|
309
|
+
.with_context(:user_data, data)
|
|
310
|
+
.continue(result.value)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
step ->(result) {
|
|
314
|
+
# All context available
|
|
315
|
+
duration = Time.now - result.context[:started_at]
|
|
316
|
+
|
|
317
|
+
result
|
|
318
|
+
.with_context(:duration, duration)
|
|
319
|
+
.continue(process(result.value))
|
|
320
|
+
}
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Combining Operations
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
step ->(result) {
|
|
328
|
+
result
|
|
329
|
+
.with_context(:timestamp, Time.now)
|
|
330
|
+
.with_context(:source, :api)
|
|
331
|
+
.with_error(:warning, "Deprecated API version")
|
|
332
|
+
.continue(transformed_data)
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Implementation Details
|
|
337
|
+
|
|
338
|
+
### Immutability
|
|
339
|
+
|
|
340
|
+
All Result methods return new instances:
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
original = SimpleFlow::Result.new(42)
|
|
344
|
+
modified = original.with_context(:key, "value")
|
|
345
|
+
|
|
346
|
+
original.context # => {}
|
|
347
|
+
modified.context # => { key: "value" }
|
|
348
|
+
|
|
349
|
+
# original and modified are different objects
|
|
350
|
+
original.object_id != modified.object_id # => true
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Internal State
|
|
354
|
+
|
|
355
|
+
The Result class maintains internal state that is preserved across method calls:
|
|
356
|
+
|
|
357
|
+
```ruby
|
|
358
|
+
result = SimpleFlow::Result.new(42)
|
|
359
|
+
.halt
|
|
360
|
+
.with_context(:key, "value")
|
|
361
|
+
|
|
362
|
+
# @continue flag is preserved
|
|
363
|
+
result.continue? # => false
|
|
364
|
+
result.context # => { key: "value" }
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Thread Safety
|
|
368
|
+
|
|
369
|
+
Result objects are immutable and thread-safe. Multiple threads can safely read from the same Result instance.
|
|
370
|
+
|
|
371
|
+
## Related Documentation
|
|
372
|
+
|
|
373
|
+
- [Pipeline API](pipeline.md) - How pipelines use Result objects
|
|
374
|
+
- [Error Handling Guide](../guides/error-handling.md) - Error handling patterns
|
|
375
|
+
- [Validation Patterns](../guides/validation-patterns.md) - Validation strategies
|