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,496 @@
1
+ # Validation Patterns
2
+
3
+ This guide presents common validation patterns used with SimpleFlow for data validation, business rule enforcement, and input sanitization.
4
+
5
+ ## Basic Validation Patterns
6
+
7
+ ### Required Fields
8
+
9
+ ```ruby
10
+ step :validate_required, ->(result) {
11
+ data = result.value
12
+ required = [:name, :email, :password]
13
+ missing = required.reject { |field| data[field] && !data[field].empty? }
14
+
15
+ if missing.any?
16
+ result.halt.with_error(:required, "Missing fields: #{missing.join(', ')}")
17
+ else
18
+ result.continue(data)
19
+ end
20
+ }
21
+ ```
22
+
23
+ ### Format Validation
24
+
25
+ ```ruby
26
+ step :validate_formats, ->(result) {
27
+ data = result.value
28
+ errors = []
29
+
30
+ unless data[:email] =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
31
+ errors << "Invalid email format"
32
+ end
33
+
34
+ unless data[:phone] =~ /\A\+?\d{10,15}\z/
35
+ errors << "Invalid phone format"
36
+ end
37
+
38
+ if errors.any?
39
+ result.halt.with_error(:format, errors.join(", "))
40
+ else
41
+ result.continue(data)
42
+ end
43
+ }
44
+ ```
45
+
46
+ ### Range Validation
47
+
48
+ ```ruby
49
+ step :validate_ranges, ->(result) {
50
+ data = result.value
51
+
52
+ if data[:age] && (data[:age] < 0 || data[:age] > 120)
53
+ return result.halt.with_error(:range, "Age must be between 0 and 120")
54
+ end
55
+
56
+ if data[:quantity] && data[:quantity] < 1
57
+ return result.halt.with_error(:range, "Quantity must be at least 1")
58
+ end
59
+
60
+ result.continue(data)
61
+ }
62
+ ```
63
+
64
+ ## Type Validation
65
+
66
+ ### Type Checking
67
+
68
+ ```ruby
69
+ step :validate_types, ->(result) {
70
+ data = result.value
71
+ type_specs = {
72
+ name: String,
73
+ age: Integer,
74
+ active: [TrueClass, FalseClass],
75
+ tags: Array
76
+ }
77
+
78
+ errors = type_specs.map do |field, expected_type|
79
+ value = data[field]
80
+ next if value.nil?
81
+
82
+ expected = Array(expected_type)
83
+ unless expected.any? { |type| value.is_a?(type) }
84
+ "#{field} must be #{expected.join(' or ')}, got #{value.class}"
85
+ end
86
+ end.compact
87
+
88
+ if errors.any?
89
+ result.halt.with_error(:type, errors.join(", "))
90
+ else
91
+ result.continue(data)
92
+ end
93
+ }
94
+ ```
95
+
96
+ ## Parallel Validation
97
+
98
+ ### Independent Field Validation
99
+
100
+ ```ruby
101
+ pipeline = SimpleFlow::Pipeline.new do
102
+ step :validate_email, ->(result) {
103
+ unless valid_email?(result.value[:email])
104
+ result.with_error(:email, "Invalid email")
105
+ end
106
+ result.continue(result.value)
107
+ }, depends_on: []
108
+
109
+ step :validate_password, ->(result) {
110
+ password = result.value[:password]
111
+ errors = []
112
+ errors << "Too short" if password.length < 8
113
+ errors << "Need uppercase" unless password =~ /[A-Z]/
114
+ errors << "Need number" unless password =~ /[0-9]/
115
+
116
+ errors.each { |err| result = result.with_error(:password, err) }
117
+ result.continue(result.value)
118
+ }, depends_on: []
119
+
120
+ step :validate_phone, ->(result) {
121
+ unless valid_phone?(result.value[:phone])
122
+ result.with_error(:phone, "Invalid phone")
123
+ end
124
+ result.continue(result.value)
125
+ }, depends_on: []
126
+
127
+ step :check_errors, ->(result) {
128
+ if result.errors.any?
129
+ result.halt(result.value)
130
+ else
131
+ result.continue(result.value)
132
+ end
133
+ }, depends_on: [:validate_email, :validate_password, :validate_phone]
134
+ end
135
+ ```
136
+
137
+ ## Business Rule Validation
138
+
139
+ ### Single Rule Validation
140
+
141
+ ```ruby
142
+ step :validate_business_rules, ->(result) {
143
+ order = result.value
144
+
145
+ # Maximum order amount
146
+ if order[:total] > 10000
147
+ return result.halt.with_error(:business, "Order exceeds maximum amount")
148
+ end
149
+
150
+ # Minimum order for free shipping
151
+ if order[:total] < 50 && order[:shipping_method] == :free
152
+ return result.halt.with_error(:business, "Free shipping requires $50 minimum")
153
+ end
154
+
155
+ # Age restriction
156
+ if order[:items].any? { |i| i[:age_restricted] } && order[:customer][:age] < 21
157
+ return result.halt.with_error(:business, "Age-restricted items require customer to be 21+")
158
+ end
159
+
160
+ result.continue(order)
161
+ }
162
+ ```
163
+
164
+ ### Conditional Business Rules
165
+
166
+ ```ruby
167
+ step :apply_discount_rules, ->(result) {
168
+ order = result.value
169
+ customer = result.context[:customer]
170
+
171
+ discount = 0
172
+
173
+ # VIP customers get 20% off
174
+ if customer[:vip]
175
+ discount = [discount, 0.20].max
176
+ end
177
+
178
+ # Orders over $100 get 10% off
179
+ if order[:subtotal] > 100
180
+ discount = [discount, 0.10].max
181
+ end
182
+
183
+ # First-time customers get 15% off
184
+ if customer[:order_count] == 0
185
+ discount = [discount, 0.15].max
186
+ end
187
+
188
+ result
189
+ .with_context(:discount_rate, discount)
190
+ .with_context(:discount_amount, order[:subtotal] * discount)
191
+ .continue(order)
192
+ }
193
+ ```
194
+
195
+ ## Custom Validators
196
+
197
+ ### Reusable Validator Classes
198
+
199
+ ```ruby
200
+ class EmailValidator
201
+ def self.call(result)
202
+ email = result.value[:email]
203
+
204
+ errors = []
205
+ errors << "Email is required" if email.nil? || email.empty?
206
+ errors << "Invalid email format" unless email =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
207
+
208
+ if errors.any?
209
+ errors.each { |error| result = result.with_error(:email, error) }
210
+ end
211
+
212
+ result.continue(result.value)
213
+ end
214
+ end
215
+
216
+ class PasswordValidator
217
+ MIN_LENGTH = 8
218
+
219
+ def self.call(result)
220
+ password = result.value[:password]
221
+
222
+ errors = []
223
+ errors << "Password is required" if password.nil? || password.empty?
224
+ errors << "Password too short" if password && password.length < MIN_LENGTH
225
+ errors << "Must contain uppercase" unless password =~ /[A-Z]/
226
+ errors << "Must contain lowercase" unless password =~ /[a-z]/
227
+ errors << "Must contain number" unless password =~ /[0-9]/
228
+
229
+ if errors.any?
230
+ errors.each { |error| result = result.with_error(:password, error) }
231
+ end
232
+
233
+ result.continue(result.value)
234
+ end
235
+ end
236
+
237
+ pipeline = SimpleFlow::Pipeline.new do
238
+ step :validate_email, EmailValidator, depends_on: []
239
+ step :validate_password, PasswordValidator, depends_on: []
240
+
241
+ step :check_validations, ->(result) {
242
+ if result.errors.any?
243
+ result.halt(result.value)
244
+ else
245
+ result.continue(result.value)
246
+ end
247
+ }, depends_on: [:validate_email, :validate_password]
248
+ end
249
+ ```
250
+
251
+ ## Cross-Field Validation
252
+
253
+ ### Dependent Fields
254
+
255
+ ```ruby
256
+ step :validate_shipping, ->(result) {
257
+ order = result.value
258
+
259
+ # If express shipping is selected, shipping address is required
260
+ if order[:shipping_method] == :express
261
+ unless order[:shipping_address]
262
+ return result.halt.with_error(:shipping, "Express shipping requires address")
263
+ end
264
+ end
265
+
266
+ # If international shipping, country is required
267
+ if order[:international]
268
+ unless order[:shipping_address][:country]
269
+ return result.halt.with_error(:shipping, "International shipping requires country")
270
+ end
271
+ end
272
+
273
+ result.continue(order)
274
+ }
275
+ ```
276
+
277
+ ### Mutually Exclusive Fields
278
+
279
+ ```ruby
280
+ step :validate_payment, ->(result) {
281
+ payment = result.value[:payment]
282
+
283
+ methods_present = [
284
+ payment[:credit_card],
285
+ payment[:paypal],
286
+ payment[:bank_transfer]
287
+ ].count { |m| m }
288
+
289
+ if methods_present == 0
290
+ result.halt.with_error(:payment, "Payment method required")
291
+ elsif methods_present > 1
292
+ result.halt.with_error(:payment, "Only one payment method allowed")
293
+ else
294
+ result.continue(result.value)
295
+ end
296
+ }
297
+ ```
298
+
299
+ ## External Validation
300
+
301
+ ### API-Based Validation
302
+
303
+ ```ruby
304
+ step :validate_address, ->(result) {
305
+ begin
306
+ address = result.value[:shipping_address]
307
+ validation = AddressValidator.validate(address)
308
+
309
+ if validation[:valid]
310
+ result
311
+ .with_context(:validated_address, validation[:normalized])
312
+ .continue(result.value)
313
+ else
314
+ result.halt.with_error(:address, validation[:errors].join(", "))
315
+ end
316
+ rescue AddressValidator::Error => e
317
+ result.halt.with_error(:validation_service, "Address validation failed: #{e.message}")
318
+ end
319
+ }
320
+ ```
321
+
322
+ ### Database Validation
323
+
324
+ ```ruby
325
+ step :validate_unique_email, ->(result) {
326
+ email = result.value[:email]
327
+
328
+ if User.exists?(email: email)
329
+ result.halt.with_error(:uniqueness, "Email already registered")
330
+ else
331
+ result.continue(result.value)
332
+ end
333
+ }
334
+
335
+ step :validate_referral_code, ->(result) {
336
+ code = result.value[:referral_code]
337
+ return result.continue(result.value) if code.nil?
338
+
339
+ referrer = User.find_by(referral_code: code)
340
+ if referrer
341
+ result.with_context(:referrer, referrer).continue(result.value)
342
+ else
343
+ result.halt.with_error(:referral, "Invalid referral code")
344
+ end
345
+ }
346
+ ```
347
+
348
+ ## Sanitization and Normalization
349
+
350
+ ### Data Cleaning
351
+
352
+ ```ruby
353
+ step :sanitize_input, ->(result) {
354
+ data = result.value
355
+
356
+ sanitized = {
357
+ name: data[:name]&.strip&.gsub(/\s+/, ' '),
358
+ email: data[:email]&.downcase&.strip,
359
+ phone: data[:phone]&.gsub(/[^\d+]/, ''),
360
+ bio: data[:bio]&.strip&.slice(0, 500)
361
+ }
362
+
363
+ result.continue(sanitized)
364
+ }
365
+ ```
366
+
367
+ ### Data Normalization
368
+
369
+ ```ruby
370
+ step :normalize_address, ->(result) {
371
+ address = result.value
372
+
373
+ normalized = {
374
+ street: address[:street]&.upcase,
375
+ city: address[:city]&.titleize,
376
+ state: address[:state]&.upcase,
377
+ zip: address[:zip]&.gsub(/[^\d-]/, ''),
378
+ country: address[:country]&.upcase
379
+ }
380
+
381
+ result.continue(normalized)
382
+ }
383
+ ```
384
+
385
+ ## Validation Middleware
386
+
387
+ ### Automatic Validation Middleware
388
+
389
+ ```ruby
390
+ class ValidationMiddleware
391
+ def initialize(callable, validator:)
392
+ @callable = callable
393
+ @validator = validator
394
+ end
395
+
396
+ def call(result)
397
+ validation_result = @validator.call(result)
398
+
399
+ if validation_result.errors.any?
400
+ validation_result.halt(validation_result.value)
401
+ else
402
+ @callable.call(validation_result)
403
+ end
404
+ end
405
+ end
406
+
407
+ pipeline = SimpleFlow::Pipeline.new do
408
+ use_middleware ValidationMiddleware, validator: EmailValidator
409
+
410
+ step ->(result) {
411
+ # Only executes if email validation passes
412
+ result.continue("Email validated: #{result.value[:email]}")
413
+ }
414
+ end
415
+ ```
416
+
417
+ ## Complete Example
418
+
419
+ ```ruby
420
+ class UserRegistrationPipeline
421
+ def self.build
422
+ SimpleFlow::Pipeline.new do
423
+ # Sanitize inputs
424
+ step :sanitize, ->(result) {
425
+ data = result.value
426
+ sanitized = {
427
+ name: data[:name]&.strip,
428
+ email: data[:email]&.downcase&.strip,
429
+ phone: data[:phone]&.gsub(/[^\d+]/, ''),
430
+ password: data[:password]
431
+ }
432
+ result.continue(sanitized)
433
+ }, depends_on: []
434
+
435
+ # Parallel validations
436
+ step :validate_name, ->(result) {
437
+ if result.value[:name].nil? || result.value[:name].empty?
438
+ result.with_error(:name, "Name is required")
439
+ else
440
+ result.continue(result.value)
441
+ end
442
+ }, depends_on: [:sanitize]
443
+
444
+ step :validate_email, EmailValidator, depends_on: [:sanitize]
445
+ step :validate_password, PasswordValidator, depends_on: [:sanitize]
446
+ step :validate_phone, ->(result) {
447
+ phone = result.value[:phone]
448
+ unless phone =~ /\A\+?\d{10,15}\z/
449
+ result.with_error(:phone, "Invalid phone format")
450
+ end
451
+ result.continue(result.value)
452
+ }, depends_on: [:sanitize]
453
+
454
+ # Check uniqueness
455
+ step :check_uniqueness, ->(result) {
456
+ if User.exists?(email: result.value[:email])
457
+ result.with_error(:email, "Email already registered")
458
+ end
459
+ result.continue(result.value)
460
+ }, depends_on: [:validate_email]
461
+
462
+ # Verify all validations passed
463
+ step :verify, ->(result) {
464
+ if result.errors.any?
465
+ result.halt(result.value)
466
+ else
467
+ result.continue(result.value)
468
+ end
469
+ }, depends_on: [:validate_name, :validate_email, :validate_password, :validate_phone, :check_uniqueness]
470
+
471
+ # Create user
472
+ step :create_user, ->(result) {
473
+ user = User.create!(result.value)
474
+ result.continue(user)
475
+ }, depends_on: [:verify]
476
+ end
477
+ end
478
+ end
479
+
480
+ # Usage
481
+ result = UserRegistrationPipeline.build.call_parallel(
482
+ SimpleFlow::Result.new(user_params)
483
+ )
484
+
485
+ if result.continue?
486
+ redirect_to dashboard_path, notice: "Welcome!"
487
+ else
488
+ render :new, errors: result.errors
489
+ end
490
+ ```
491
+
492
+ ## Related Documentation
493
+
494
+ - [Error Handling](error-handling.md) - Comprehensive error handling strategies
495
+ - [Complex Workflows](complex-workflows.md) - Building sophisticated pipelines
496
+ - [Result API](../api/result.md) - Result class reference
data/docs/index.md ADDED
@@ -0,0 +1,169 @@
1
+ # SimpleFlow
2
+
3
+ <div align="center">
4
+
5
+ ![SimpleFlow](https://img.shields.io/badge/SimpleFlow-Pipeline%20Framework-indigo?style=for-the-badge)
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/simple_flow.svg)](https://badge.fury.io/rb/simple_flow)
8
+ [![CI](https://github.com/MadBomber/simple_flow/workflows/CI/badge.svg)](https://github.com/MadBomber/simple_flow/actions)
9
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2.0-red.svg)](https://www.ruby-lang.org/)
10
+
11
+ **A lightweight, modular Ruby framework for building composable data processing pipelines with concurrent execution.**
12
+
13
+ [Get Started](getting-started/quick-start.md){ .md-button .md-button--primary }
14
+ [View on GitHub](https://github.com/MadBomber/simple_flow){ .md-button }
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## Overview
21
+
22
+ SimpleFlow provides a clean and flexible architecture for orchestrating multi-step workflows. It emphasizes simplicity, composability, and performance through fiber-based concurrent execution.
23
+
24
+ ## Key Features
25
+
26
+ ### 🔄 Concurrent Execution
27
+ Run independent steps in parallel using the Async gem for significant performance improvements.
28
+
29
+ ```ruby
30
+ pipeline = SimpleFlow::Pipeline.new do
31
+ parallel do
32
+ step ->(result) { fetch_orders(result) }
33
+ step ->(result) { fetch_preferences(result) }
34
+ step ->(result) { fetch_analytics(result) }
35
+ end
36
+ end
37
+ ```
38
+
39
+ ### 🔗 Composable Pipelines
40
+ Build complex workflows from simple, reusable steps with an intuitive DSL.
41
+
42
+ ```ruby
43
+ pipeline = SimpleFlow::Pipeline.new do
44
+ step ->(result) { validate(result) }
45
+ step ->(result) { transform(result) }
46
+ step ->(result) { save(result) }
47
+ end
48
+ ```
49
+
50
+ ### 🛡️ Immutable Results
51
+ Thread-safe result objects with context and error tracking throughout the pipeline.
52
+
53
+ ```ruby
54
+ result = SimpleFlow::Result.new(data)
55
+ .with_context(:user_id, 123)
56
+ .with_error(:validation, "Invalid format")
57
+ .continue(processed_data)
58
+ ```
59
+
60
+ ### 🔌 Middleware Support
61
+ Apply cross-cutting concerns like logging and instrumentation to all steps.
62
+
63
+ ```ruby
64
+ pipeline = SimpleFlow::Pipeline.new do
65
+ use_middleware SimpleFlow::MiddleWare::Logging
66
+ use_middleware SimpleFlow::MiddleWare::Instrumentation
67
+
68
+ step ->(result) { process(result) }
69
+ end
70
+ ```
71
+
72
+ ### ⚡ Flow Control
73
+ Halt execution early or continue based on step outcomes with built-in mechanisms.
74
+
75
+ ```ruby
76
+ step ->(result) {
77
+ if result.value < 0
78
+ result.halt.with_error(:validation, "Value must be positive")
79
+ else
80
+ result.continue(result.value)
81
+ end
82
+ }
83
+ ```
84
+
85
+ ### 📊 Built for Performance
86
+ Fiber-based concurrency without threading overhead, ideal for I/O-bound operations.
87
+
88
+ **Performance Example:**
89
+ - Sequential: ~0.4s (4 × 0.1s operations)
90
+ - Parallel: ~0.1s (4 concurrent operations)
91
+ - **4x speedup!**
92
+
93
+ ## Quick Example
94
+
95
+ ```ruby
96
+ require 'simple_flow'
97
+
98
+ # Build a user data pipeline
99
+ pipeline = SimpleFlow::Pipeline.new do
100
+ step ->(result) { validate_user(result) }
101
+
102
+ parallel do
103
+ step ->(result) { fetch_profile(result) }
104
+ step ->(result) { fetch_orders(result) }
105
+ step ->(result) { fetch_analytics(result) }
106
+ end
107
+
108
+ step ->(result) { aggregate_data(result) }
109
+ end
110
+
111
+ result = pipeline.call(SimpleFlow::Result.new(user_id: 123))
112
+ ```
113
+
114
+ ## Why SimpleFlow?
115
+
116
+ - **Simple**: Minimal API surface, maximum power
117
+ - **Fast**: Fiber-based concurrency for I/O-bound operations
118
+ - **Safe**: Immutable results prevent race conditions
119
+ - **Flexible**: Middleware and flow control for any use case
120
+ - **Testable**: Easy to unit test individual steps
121
+ - **Production-Ready**: Used in real-world applications
122
+
123
+ ## Next Steps
124
+
125
+ <div class="grid cards" markdown>
126
+
127
+ - :material-clock-fast:{ .lg .middle } __Quick Start__
128
+
129
+ ---
130
+
131
+ Get up and running in 5 minutes
132
+
133
+ [:octicons-arrow-right-24: Quick Start](getting-started/quick-start.md)
134
+
135
+ - :material-book-open-variant:{ .lg .middle } __Core Concepts__
136
+
137
+ ---
138
+
139
+ Learn the fundamental concepts
140
+
141
+ [:octicons-arrow-right-24: Core Concepts](core-concepts/overview.md)
142
+
143
+ - :material-lightning-bolt:{ .lg .middle } __Concurrent Execution__
144
+
145
+ ---
146
+
147
+ Maximize performance with parallel steps
148
+
149
+ [:octicons-arrow-right-24: Concurrency Guide](concurrent/introduction.md)
150
+
151
+ - :material-code-braces:{ .lg .middle } __Examples__
152
+
153
+ ---
154
+
155
+ Real-world examples and patterns
156
+
157
+ [:octicons-arrow-right-24: Examples](getting-started/examples.md)
158
+
159
+ </div>
160
+
161
+ ## Community & Support
162
+
163
+ - :fontawesome-brands-github: [GitHub Repository](https://github.com/MadBomber/simple_flow)
164
+ - :material-bug: [Issue Tracker](https://github.com/MadBomber/simple_flow/issues)
165
+ - :material-file-document: [Changelog](https://github.com/MadBomber/simple_flow/blob/main/CHANGELOG.md)
166
+
167
+ ## License
168
+
169
+ SimpleFlow is released under the [MIT License](https://github.com/MadBomber/simple_flow/blob/main/LICENSE).
@@ -0,0 +1,3 @@
1
+ *.html
2
+ *.mmd
3
+ *.dot