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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +52 -0
  4. data/.rubocop.yml +57 -0
  5. data/CHANGELOG.md +4 -0
  6. data/COMMITS.md +196 -0
  7. data/LICENSE +21 -0
  8. data/README.md +481 -0
  9. data/Rakefile +15 -0
  10. data/benchmarks/parallel_vs_sequential.rb +98 -0
  11. data/benchmarks/pipeline_overhead.rb +130 -0
  12. data/docs/api/middleware.md +468 -0
  13. data/docs/api/parallel-step.md +363 -0
  14. data/docs/api/pipeline.md +382 -0
  15. data/docs/api/result.md +375 -0
  16. data/docs/concurrent/best-practices.md +687 -0
  17. data/docs/concurrent/introduction.md +246 -0
  18. data/docs/concurrent/parallel-steps.md +418 -0
  19. data/docs/concurrent/performance.md +481 -0
  20. data/docs/core-concepts/flow-control.md +452 -0
  21. data/docs/core-concepts/middleware.md +389 -0
  22. data/docs/core-concepts/overview.md +219 -0
  23. data/docs/core-concepts/pipeline.md +315 -0
  24. data/docs/core-concepts/result.md +168 -0
  25. data/docs/core-concepts/steps.md +391 -0
  26. data/docs/development/benchmarking.md +443 -0
  27. data/docs/development/contributing.md +380 -0
  28. data/docs/development/dagwood-concepts.md +435 -0
  29. data/docs/development/testing.md +514 -0
  30. data/docs/getting-started/examples.md +197 -0
  31. data/docs/getting-started/installation.md +62 -0
  32. data/docs/getting-started/quick-start.md +218 -0
  33. data/docs/guides/choosing-concurrency-model.md +441 -0
  34. data/docs/guides/complex-workflows.md +440 -0
  35. data/docs/guides/data-fetching.md +478 -0
  36. data/docs/guides/error-handling.md +635 -0
  37. data/docs/guides/file-processing.md +505 -0
  38. data/docs/guides/validation-patterns.md +496 -0
  39. data/docs/index.md +169 -0
  40. data/examples/.gitignore +3 -0
  41. data/examples/01_basic_pipeline.rb +112 -0
  42. data/examples/02_error_handling.rb +178 -0
  43. data/examples/03_middleware.rb +186 -0
  44. data/examples/04_parallel_automatic.rb +221 -0
  45. data/examples/05_parallel_explicit.rb +279 -0
  46. data/examples/06_real_world_ecommerce.rb +288 -0
  47. data/examples/07_real_world_etl.rb +277 -0
  48. data/examples/08_graph_visualization.rb +246 -0
  49. data/examples/09_pipeline_visualization.rb +266 -0
  50. data/examples/10_concurrency_control.rb +235 -0
  51. data/examples/11_sequential_dependencies.rb +243 -0
  52. data/examples/12_none_constant.rb +161 -0
  53. data/examples/README.md +374 -0
  54. data/examples/regression_test/01_basic_pipeline.txt +38 -0
  55. data/examples/regression_test/02_error_handling.txt +92 -0
  56. data/examples/regression_test/03_middleware.txt +61 -0
  57. data/examples/regression_test/04_parallel_automatic.txt +86 -0
  58. data/examples/regression_test/05_parallel_explicit.txt +80 -0
  59. data/examples/regression_test/06_real_world_ecommerce.txt +53 -0
  60. data/examples/regression_test/07_real_world_etl.txt +58 -0
  61. data/examples/regression_test/08_graph_visualization.txt +429 -0
  62. data/examples/regression_test/09_pipeline_visualization.txt +305 -0
  63. data/examples/regression_test/10_concurrency_control.txt +96 -0
  64. data/examples/regression_test/11_sequential_dependencies.txt +86 -0
  65. data/examples/regression_test/12_none_constant.txt +64 -0
  66. data/examples/regression_test.rb +105 -0
  67. data/lib/simple_flow/dependency_graph.rb +120 -0
  68. data/lib/simple_flow/dependency_graph_visualizer.rb +326 -0
  69. data/lib/simple_flow/middleware.rb +36 -0
  70. data/lib/simple_flow/parallel_executor.rb +80 -0
  71. data/lib/simple_flow/pipeline.rb +405 -0
  72. data/lib/simple_flow/result.rb +88 -0
  73. data/lib/simple_flow/step_tracker.rb +58 -0
  74. data/lib/simple_flow/version.rb +5 -0
  75. data/lib/simple_flow.rb +41 -0
  76. data/mkdocs.yml +146 -0
  77. data/pipeline_graph.dot +51 -0
  78. data/pipeline_graph.html +60 -0
  79. data/pipeline_graph.mmd +19 -0
  80. metadata +127 -0
@@ -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