desiru 0.1.0 → 0.1.1

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +34 -0
  3. data/.rubocop.yml +7 -4
  4. data/.ruby-version +1 -0
  5. data/CLAUDE.md +4 -0
  6. data/Gemfile +21 -2
  7. data/Gemfile.lock +87 -12
  8. data/README.md +295 -2
  9. data/Rakefile +1 -0
  10. data/db/migrations/001_create_initial_tables.rb +96 -0
  11. data/db/migrations/002_create_job_results.rb +39 -0
  12. data/desiru.db +0 -0
  13. data/desiru.gemspec +2 -5
  14. data/docs/background_processing_roadmap.md +87 -0
  15. data/docs/job_scheduling.md +167 -0
  16. data/dspy-analysis-swarm.yml +60 -0
  17. data/dspy-feature-analysis.md +121 -0
  18. data/examples/README.md +69 -0
  19. data/examples/api_with_persistence.rb +122 -0
  20. data/examples/assertions_example.rb +232 -0
  21. data/examples/async_processing.rb +2 -0
  22. data/examples/few_shot_learning.rb +1 -2
  23. data/examples/graphql_api.rb +4 -2
  24. data/examples/graphql_integration.rb +3 -3
  25. data/examples/graphql_optimization_summary.md +143 -0
  26. data/examples/graphql_performance_benchmark.rb +247 -0
  27. data/examples/persistence_example.rb +102 -0
  28. data/examples/react_agent.rb +203 -0
  29. data/examples/rest_api.rb +173 -0
  30. data/examples/rest_api_advanced.rb +333 -0
  31. data/examples/scheduled_job_example.rb +116 -0
  32. data/examples/simple_qa.rb +1 -2
  33. data/examples/sinatra_api.rb +109 -0
  34. data/examples/typed_signatures.rb +1 -2
  35. data/graphql_optimization_summary.md +53 -0
  36. data/lib/desiru/api/grape_integration.rb +284 -0
  37. data/lib/desiru/api/persistence_middleware.rb +148 -0
  38. data/lib/desiru/api/sinatra_integration.rb +217 -0
  39. data/lib/desiru/api.rb +42 -0
  40. data/lib/desiru/assertions.rb +74 -0
  41. data/lib/desiru/async_status.rb +65 -0
  42. data/lib/desiru/cache.rb +1 -1
  43. data/lib/desiru/configuration.rb +2 -1
  44. data/lib/desiru/errors.rb +160 -0
  45. data/lib/desiru/field.rb +17 -14
  46. data/lib/desiru/graphql/batch_loader.rb +85 -0
  47. data/lib/desiru/graphql/data_loader.rb +242 -75
  48. data/lib/desiru/graphql/enum_builder.rb +75 -0
  49. data/lib/desiru/graphql/executor.rb +37 -4
  50. data/lib/desiru/graphql/schema_generator.rb +62 -158
  51. data/lib/desiru/graphql/type_builder.rb +138 -0
  52. data/lib/desiru/graphql/type_cache_warmer.rb +91 -0
  53. data/lib/desiru/jobs/async_predict.rb +1 -1
  54. data/lib/desiru/jobs/base.rb +67 -0
  55. data/lib/desiru/jobs/batch_processor.rb +6 -6
  56. data/lib/desiru/jobs/retriable.rb +119 -0
  57. data/lib/desiru/jobs/retry_strategies.rb +169 -0
  58. data/lib/desiru/jobs/scheduler.rb +219 -0
  59. data/lib/desiru/jobs/webhook_notifier.rb +242 -0
  60. data/lib/desiru/models/anthropic.rb +164 -0
  61. data/lib/desiru/models/base.rb +37 -3
  62. data/lib/desiru/models/open_ai.rb +151 -0
  63. data/lib/desiru/models/open_router.rb +161 -0
  64. data/lib/desiru/module.rb +59 -9
  65. data/lib/desiru/modules/chain_of_thought.rb +3 -3
  66. data/lib/desiru/modules/majority.rb +51 -0
  67. data/lib/desiru/modules/multi_chain_comparison.rb +204 -0
  68. data/lib/desiru/modules/predict.rb +8 -1
  69. data/lib/desiru/modules/program_of_thought.rb +139 -0
  70. data/lib/desiru/modules/react.rb +273 -0
  71. data/lib/desiru/modules/retrieve.rb +4 -2
  72. data/lib/desiru/optimizers/base.rb +2 -4
  73. data/lib/desiru/optimizers/bootstrap_few_shot.rb +2 -2
  74. data/lib/desiru/optimizers/copro.rb +268 -0
  75. data/lib/desiru/optimizers/knn_few_shot.rb +185 -0
  76. data/lib/desiru/persistence/database.rb +71 -0
  77. data/lib/desiru/persistence/models/api_request.rb +38 -0
  78. data/lib/desiru/persistence/models/job_result.rb +138 -0
  79. data/lib/desiru/persistence/models/module_execution.rb +37 -0
  80. data/lib/desiru/persistence/models/optimization_result.rb +28 -0
  81. data/lib/desiru/persistence/models/training_example.rb +25 -0
  82. data/lib/desiru/persistence/models.rb +11 -0
  83. data/lib/desiru/persistence/repositories/api_request_repository.rb +98 -0
  84. data/lib/desiru/persistence/repositories/base_repository.rb +77 -0
  85. data/lib/desiru/persistence/repositories/job_result_repository.rb +116 -0
  86. data/lib/desiru/persistence/repositories/module_execution_repository.rb +85 -0
  87. data/lib/desiru/persistence/repositories/optimization_result_repository.rb +67 -0
  88. data/lib/desiru/persistence/repositories/training_example_repository.rb +102 -0
  89. data/lib/desiru/persistence/repository.rb +29 -0
  90. data/lib/desiru/persistence/setup.rb +77 -0
  91. data/lib/desiru/persistence.rb +49 -0
  92. data/lib/desiru/registry.rb +3 -5
  93. data/lib/desiru/signature.rb +91 -24
  94. data/lib/desiru/version.rb +1 -1
  95. data/lib/desiru.rb +23 -8
  96. data/missing-features-analysis.md +192 -0
  97. metadata +63 -45
  98. data/lib/desiru/models/raix_adapter.rb +0 -210
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'desiru'
6
+ require 'desiru/persistence'
7
+ require 'rack'
8
+
9
+ # Configure Desiru
10
+ Desiru.configure do |config|
11
+ config.default_model = Desiru::Models::OpenAI.new(
12
+ api_key: ENV['OPENAI_API_KEY'] || 'your-api-key',
13
+ model: 'gpt-3.5-turbo'
14
+ )
15
+ end
16
+
17
+ # Setup persistence
18
+ puts "Setting up database..."
19
+ Desiru::Persistence.database_url = 'sqlite://api_tracking.db'
20
+ Desiru::Persistence.connect!
21
+ Desiru::Persistence.migrate!
22
+
23
+ # Define a simple module
24
+ class TextAnalyzer < Desiru::Module
25
+ signature 'TextAnalyzer', 'Analyze text sentiment and key themes'
26
+
27
+ input 'text', type: 'string', desc: 'Text to analyze'
28
+
29
+ output 'sentiment', type: 'string', desc: 'Overall sentiment (positive/negative/neutral)'
30
+ output 'themes', type: 'list[string]', desc: 'Key themes identified'
31
+ output 'confidence', type: 'float', desc: 'Confidence score (0-1)'
32
+
33
+ def forward(_text:)
34
+ # Simulate analysis
35
+ {
36
+ sentiment: %w[positive negative neutral].sample,
37
+ themes: %w[technology business health education].sample(2),
38
+ confidence: rand(0.7..0.95).round(2)
39
+ }
40
+ end
41
+ end
42
+
43
+ # Create API with persistence tracking
44
+ api = Desiru::API.create(framework: :sinatra) do
45
+ register_module '/analyze', TextAnalyzer.new,
46
+ description: 'Analyze text sentiment and themes'
47
+ end
48
+
49
+ # Add persistence tracking
50
+ app = api.with_persistence(enabled: true)
51
+
52
+ # Add a simple UI endpoint
53
+ ui_app = Rack::Builder.new do
54
+ use Desiru::API::PersistenceMiddleware
55
+
56
+ map '/' do
57
+ run lambda { |_env|
58
+ html = <<~HTML
59
+ <!DOCTYPE html>
60
+ <html>
61
+ <head>
62
+ <title>Desiru API with Persistence</title>
63
+ <style>
64
+ body { font-family: Arial, sans-serif; margin: 40px; }
65
+ .endpoint { background: #f0f0f0; padding: 10px; margin: 10px 0; }
66
+ .stats { background: #e0f0ff; padding: 15px; margin: 20px 0; }
67
+ pre { background: #f5f5f5; padding: 10px; overflow-x: auto; }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ <h1>Desiru API with Persistence Tracking</h1>
72
+ #{' '}
73
+ <div class="endpoint">
74
+ <h2>Text Analysis Endpoint</h2>
75
+ <p><strong>POST /api/v1/analyze</strong></p>
76
+ <p>Analyze text sentiment and extract key themes</p>
77
+ <pre>curl -X POST http://localhost:9294/api/v1/analyze \\
78
+ -H "Content-Type: application/json" \\
79
+ -d '{"text": "This is an amazing product that exceeds expectations!"}'</pre>
80
+ </div>
81
+ #{' '}
82
+ <div class="stats">
83
+ <h2>API Statistics</h2>
84
+ <ul>
85
+ <li>Total API Requests: #{Desiru::Persistence[:api_requests].count}</li>
86
+ <li>Module Executions: #{Desiru::Persistence[:module_executions].count}</li>
87
+ <li>Success Rate: #{Desiru::Persistence[:module_executions].success_rate}%</li>
88
+ <li>Average Response Time: #{Desiru::Persistence[:api_requests].average_response_time || 0}s</li>
89
+ </ul>
90
+ </div>
91
+ #{' '}
92
+ <div class="endpoint">
93
+ <h2>Recent Requests</h2>
94
+ <ul>
95
+ #{Desiru::Persistence[:api_requests].recent(5).map do |r|
96
+ "<li>#{r.method} #{r.path} - #{r.status_code} (#{r.response_time ? "#{(r.response_time * 1000).round}ms" : 'N/A'})</li>"
97
+ end.join("\n ")}
98
+ </ul>
99
+ </div>
100
+ </body>
101
+ </html>
102
+ HTML
103
+
104
+ [200, { 'Content-Type' => 'text/html' }, [html]]
105
+ }
106
+ end
107
+
108
+ map '/api' do
109
+ run app
110
+ end
111
+ end
112
+
113
+ puts "Starting API server with persistence tracking on http://localhost:9294"
114
+ puts "\nEndpoints:"
115
+ puts " GET / - Web UI with statistics"
116
+ puts " POST /api/v1/analyze - Text analysis endpoint"
117
+ puts " GET /api/v1/health - Health check"
118
+ puts "\nAll API requests are automatically tracked in the database!"
119
+ puts "Press Ctrl+C to stop the server"
120
+
121
+ # Start the server
122
+ Rack::Handler::WEBrick.run ui_app, Port: 9294
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'desiru'
6
+
7
+ # Mock model for demonstration
8
+ class MockModel
9
+ def complete(_messages:, **_options)
10
+ # Simple mock that returns predefined responses
11
+ { choices: [{ message: { content: "Mock response" } }] }
12
+ end
13
+ end
14
+
15
+ # Configure Desiru with assertions
16
+ Desiru.configure do |config|
17
+ config.default_model = MockModel.new
18
+ config.logger = Logger.new($stdout).tap do |log|
19
+ log.level = Logger::INFO
20
+ log.formatter = proc do |severity, datetime, _, msg|
21
+ "[#{severity}] #{datetime}: #{msg}\n"
22
+ end
23
+ end
24
+ end
25
+
26
+ # Configure assertion behavior
27
+ Desiru::Assertions.configure do |config|
28
+ config.max_assertion_retries = 2
29
+ config.assertion_retry_delay = 0.5
30
+ end
31
+
32
+ # Example 1: Module with confidence assertion
33
+ class FactChecker < Desiru::Module
34
+ def forward(statement:)
35
+ # Simulate fact checking with confidence score
36
+ facts = [
37
+ { statement: "The sky is blue", confidence: 0.95 },
38
+ { statement: "Water boils at 100°C", confidence: 0.98 },
39
+ { statement: "Cats can fly", confidence: 0.1 },
40
+ { statement: "The Earth is flat", confidence: 0.05 }
41
+ ]
42
+
43
+ # Find confidence for the statement
44
+ fact = facts.find { |f| f[:statement].downcase == statement.downcase }
45
+ confidence = fact ? fact[:confidence] : rand(0.3..0.9)
46
+
47
+ result = {
48
+ statement: statement,
49
+ confidence: confidence,
50
+ verified: confidence > 0.7
51
+ }
52
+
53
+ # Assert high confidence for fact verification
54
+ Desiru.assert(
55
+ result[:confidence] > 0.7,
56
+ "Low confidence score: #{result[:confidence]}. Cannot verify statement."
57
+ )
58
+
59
+ result
60
+ end
61
+ end
62
+
63
+ # Example 2: Module with suggestions for best practices
64
+ class CodeReviewer < Desiru::Module
65
+ def forward(code:, language:)
66
+ review = {
67
+ code: code,
68
+ language: language,
69
+ issues: [],
70
+ suggestions: []
71
+ }
72
+
73
+ # Simulate code analysis
74
+ if code.include?('TODO')
75
+ review[:issues] << "Found TODO comment"
76
+ review[:suggestions] << "Consider creating a ticket for TODO items"
77
+ end
78
+
79
+ if language == 'ruby' && !code.include?('frozen_string_literal')
80
+ review[:suggestions] << "Add frozen_string_literal pragma"
81
+ end
82
+
83
+ # Suggest having tests
84
+ Desiru.suggest(
85
+ code.include?('test') || code.include?('spec'),
86
+ "No tests found in the code. Consider adding test coverage."
87
+ )
88
+
89
+ # Suggest documentation
90
+ Desiru.suggest(
91
+ code.include?('#') || code.include?('/**'),
92
+ "No comments found. Consider adding documentation."
93
+ )
94
+
95
+ review[:score] = 100 - (review[:issues].length * 10)
96
+ review
97
+ end
98
+ end
99
+
100
+ # Example 3: Module combining assertions and suggestions
101
+ class DataValidator < Desiru::Module
102
+ def forward(data:, schema:)
103
+ validation = {
104
+ data: data,
105
+ valid: true,
106
+ errors: [],
107
+ warnings: []
108
+ }
109
+
110
+ # Required field assertion
111
+ schema[:required]&.each do |field|
112
+ if !data.key?(field) || data[field].nil?
113
+ validation[:valid] = false
114
+ validation[:errors] << "Missing required field: #{field}"
115
+ end
116
+ end
117
+
118
+ # Assert data is valid
119
+ Desiru.assert(
120
+ validation[:valid],
121
+ "Data validation failed: #{validation[:errors].join(', ')}"
122
+ )
123
+
124
+ # Suggest best practices
125
+ if data.is_a?(Hash)
126
+ Desiru.suggest(
127
+ data.keys.all? { |k| k.is_a?(Symbol) },
128
+ "Consider using symbols for hash keys for better performance"
129
+ )
130
+ end
131
+
132
+ # Check data types (suggestions)
133
+ schema[:types]&.each do |field, expected_type|
134
+ next unless data.key?(field)
135
+
136
+ actual_type = data[field].class
137
+ Desiru.suggest(
138
+ actual_type == expected_type,
139
+ "Field '#{field}' is #{actual_type}, expected #{expected_type}"
140
+ )
141
+ end
142
+
143
+ validation
144
+ end
145
+ end
146
+
147
+ # Demonstrate the modules
148
+ puts "=== Assertion Examples ==="
149
+ puts
150
+
151
+ # Example 1: Fact Checker with passing assertion
152
+ puts "1. Fact Checker - Valid Statement:"
153
+ fact_checker = FactChecker.new('statement:str -> statement:str, confidence:float, verified:bool')
154
+ begin
155
+ result = fact_checker.call(statement: "Water boils at 100°C")
156
+ puts " ✓ Statement: #{result[:statement]}"
157
+ puts " ✓ Confidence: #{result[:confidence]}"
158
+ puts " ✓ Verified: #{result[:verified]}"
159
+ rescue Desiru::Assertions::AssertionError => e
160
+ puts " ✗ Assertion failed: #{e.message}"
161
+ end
162
+ puts
163
+
164
+ # Example 2: Fact Checker with failing assertion
165
+ puts "2. Fact Checker - False Statement:"
166
+ begin
167
+ result = fact_checker.call(statement: "Cats can fly")
168
+ puts " ✓ Statement verified with confidence: #{result[:confidence]}"
169
+ rescue Desiru::Assertions::AssertionError => e
170
+ puts " ✗ Assertion failed after retries: #{e.message}"
171
+ puts " ✗ Module: #{e.module_name}"
172
+ puts " ✗ Retries: #{e.retry_count}"
173
+ end
174
+ puts
175
+
176
+ # Example 3: Code Reviewer with suggestions
177
+ puts "3. Code Reviewer - With Suggestions:"
178
+ code_reviewer = CodeReviewer.new(
179
+ 'code:str, language:str -> code:str, language:str, issues:list, suggestions:list, score:int'
180
+ )
181
+ code = <<~RUBY
182
+ def calculate_sum(numbers)
183
+ # TODO: Add validation
184
+ numbers.sum
185
+ end
186
+ RUBY
187
+
188
+ result = code_reviewer.call(code: code, language: 'ruby')
189
+ puts " Code review score: #{result[:score]}"
190
+ puts " Issues: #{result[:issues].join(', ')}"
191
+ puts " Suggestions: #{result[:suggestions].join(', ')}"
192
+ puts
193
+
194
+ # Example 4: Data Validator with mixed validations
195
+ puts "4. Data Validator - Complete Example:"
196
+ validator = DataValidator.new('data:dict, schema:dict -> data:dict, valid:bool, errors:list, warnings:list')
197
+
198
+ schema = {
199
+ required: %i[name email],
200
+ types: {
201
+ name: String,
202
+ email: String,
203
+ age: Integer
204
+ }
205
+ }
206
+
207
+ # Valid data
208
+ puts " a) Valid data:"
209
+ begin
210
+ valid_data = { name: "John Doe", email: "john@example.com", age: 30 }
211
+ result = validator.call(data: valid_data, schema: schema)
212
+ puts " ✓ Validation passed"
213
+ puts " ✓ Data is valid: #{result[:valid]}"
214
+ rescue Desiru::Assertions::AssertionError => e
215
+ puts " ✗ Validation failed: #{e.message}"
216
+ end
217
+
218
+ # Invalid data
219
+ puts " b) Invalid data (missing required field):"
220
+ begin
221
+ invalid_data = { name: "Jane Doe", age: "twenty-five" }
222
+ validator.call(data: invalid_data, schema: schema)
223
+ puts " ✓ Validation passed"
224
+ rescue Desiru::Assertions::AssertionError => e
225
+ puts " ✗ Validation failed: #{e.message}"
226
+ end
227
+
228
+ puts
229
+ puts "=== Assertion Configuration ==="
230
+ puts "Max assertion retries: #{Desiru::Assertions.configuration.max_assertion_retries}"
231
+ puts "Retry delay: #{Desiru::Assertions.configuration.assertion_retry_delay}s"
232
+ puts "Assertions logged: #{Desiru::Assertions.configuration.log_assertions}"
@@ -52,6 +52,8 @@ puts " Status: Processing..."
52
52
  # Check if ready (non-blocking)
53
53
  sleep(0.1)
54
54
  puts " Ready? #{result.ready?}"
55
+ puts " Status: #{result.status}"
56
+ puts " Progress: #{result.progress}%" if result.progress
55
57
 
56
58
  # Wait for result (blocking with timeout)
57
59
  begin
@@ -6,8 +6,7 @@ require 'desiru'
6
6
 
7
7
  # Configure Desiru
8
8
  Desiru.configure do |config|
9
- config.default_model = Desiru::Models::RaixAdapter.new(
10
- provider: :openai,
9
+ config.default_model = Desiru::Models::OpenAI.new(
11
10
  model: 'gpt-3.5-turbo',
12
11
  api_key: ENV['OPENAI_API_KEY'] || raise('Please set OPENAI_API_KEY environment variable')
13
12
  )
@@ -8,8 +8,10 @@ require 'desiru/graphql/executor'
8
8
 
9
9
  # Configure Desiru
10
10
  Desiru.configure do |config|
11
- # Use a mock model for demonstration
12
- config.default_model = Desiru::Models::RaixAdapter.new
11
+ # Use OpenAI model for demonstration
12
+ config.default_model = Desiru::Models::OpenAI.new(
13
+ api_key: ENV['OPENAI_API_KEY'] || raise('Please set OPENAI_API_KEY environment variable')
14
+ )
13
15
  end
14
16
 
15
17
  # Create some example modules
@@ -10,9 +10,9 @@ require 'desiru/graphql/schema_generator'
10
10
 
11
11
  # Configure Desiru
12
12
  Desiru.configure do |config|
13
- config.default_model = Desiru::Models::RaixAdapter.new(
14
- client: 'openai',
15
- model: 'gpt-3.5-turbo'
13
+ config.default_model = Desiru::Models::OpenAI.new(
14
+ model: 'gpt-3.5-turbo',
15
+ api_key: ENV['OPENAI_API_KEY'] || raise('Please set OPENAI_API_KEY environment variable')
16
16
  )
17
17
  end
18
18
 
@@ -0,0 +1,143 @@
1
+ # GraphQL DataLoader Optimization: Request Deduplication & Code Quality Improvements
2
+
3
+ ## Overview
4
+
5
+ I've implemented request deduplication in the GraphQL DataLoader to prevent duplicate operations and improve performance. This optimization is particularly beneficial for GraphQL APIs that handle complex queries with repeated fields. Additionally, I've refactored the code for better maintainability and added VCR integration for reproducible testing.
6
+
7
+ ## Changes Made
8
+
9
+ ### 1. Enhanced DataLoader (`lib/desiru/graphql/data_loader.rb`)
10
+ - Added `@pending_promises` tracking to detect duplicate requests
11
+ - Added `@mutex` for thread-safe operations
12
+ - Modified `perform_loads` to group identical requests and process only unique ones
13
+ - All duplicate requests receive the same result, preventing redundant processing
14
+
15
+ ### 2. Updated BatchLoader
16
+ - Added `check_pending_promise` method to detect existing promises for the same inputs
17
+ - Modified `load` method to return existing promises for duplicate requests
18
+ - Ensures thread-safe promise management
19
+
20
+ ### 3. Key Implementation Details
21
+
22
+ **Deduplication Logic:**
23
+ ```ruby
24
+ # Group by unique inputs to deduplicate
25
+ unique_inputs_map = {}
26
+ promises_by_inputs = Hash.new { |h, k| h[k] = [] }
27
+
28
+ batch.each do |inputs, promise|
29
+ input_key = inputs.sort.to_h.hash
30
+ unique_inputs_map[input_key] = inputs
31
+ promises_by_inputs[input_key] << promise
32
+ end
33
+
34
+ # Process only unique inputs
35
+ unique_inputs = unique_inputs_map.values
36
+ ```
37
+
38
+ **Thread Safety:**
39
+ - All shared state modifications are wrapped in mutex synchronization
40
+ - Promise fulfillment is handled atomically
41
+ - Concurrent duplicate requests are properly handled
42
+
43
+ ## Performance Impact
44
+
45
+ The benchmark results show significant improvements:
46
+
47
+ 1. **Query with 6 fields (3 unique)**: 89.5% improvement
48
+ 2. **Nested query simulation**: 14.9% improvement
49
+ 3. **Large batch (50 fields, 10 unique)**: 6.1% improvement with 5:1 deduplication ratio
50
+
51
+ ## Benefits
52
+
53
+ 1. **Prevents N+1 Problems**: Multiple requests for the same data are automatically deduplicated
54
+ 2. **Improved Response Times**: Fewer actual module executions mean faster responses
55
+ 3. **Resource Efficiency**: Reduces load on backend systems and LLMs
56
+ 4. **Thread Safe**: Properly handles concurrent requests
57
+ 5. **Transparent**: Works automatically without changes to GraphQL schemas or queries
58
+
59
+ ## Testing
60
+
61
+ Added comprehensive test coverage including:
62
+ - Basic deduplication scenarios
63
+ - Different request patterns
64
+ - Cache interaction
65
+ - Key ordering independence
66
+ - Concurrent request handling
67
+ - Thread safety verification
68
+
69
+ All tests pass successfully.
70
+
71
+ ## Usage
72
+
73
+ The optimization works automatically when using the GraphQL DataLoader:
74
+
75
+ ```ruby
76
+ data_loader = Desiru::GraphQL::DataLoader.new
77
+ executor = Desiru::GraphQL::Executor.new(schema, data_loader: data_loader)
78
+
79
+ # Duplicate requests in the query are automatically deduplicated
80
+ result = executor.execute(graphql_query)
81
+ ```
82
+
83
+ ## Code Quality Improvements
84
+
85
+ ### Refactored Complex Methods
86
+ - Split `perform_loads` method into smaller, focused methods:
87
+ - `process_loader_batch` - Handles individual loader batches
88
+ - `deduplicate_batch` - Extracts deduplication logic
89
+ - `execute_batch` - Handles batch execution and error handling
90
+ - `fulfill_promises` - Manages promise fulfillment
91
+ - Reduced method complexity from ABC size 43.69 to under 25
92
+ - Improved code readability and maintainability
93
+
94
+ ### Architectural Improvements
95
+ - **Extracted TypeBuilder Module**: Moved GraphQL type generation logic into a separate module
96
+ - Reduced SchemaGenerator class length by ~140 lines
97
+ - Better separation of concerns
98
+ - Improved testability and reusability
99
+ - **Fixed Linting Issues**:
100
+ - Converted class variables to class instance variables in SchemaGenerator
101
+ - Fixed predicate method naming (`has_pending_loads?` → `pending_loads?`)
102
+ - Eliminated duplicate branch conditions in type resolution
103
+ - **Maintained Performance**: All optimizations preserved with 83.9% improvement for duplicate requests
104
+
105
+ ### VCR Integration for Testing
106
+ Added comprehensive VCR support for GraphQL testing:
107
+ - **GraphQLVCRHelper** module for easy VCR configuration
108
+ - Custom GraphQL operation matching for accurate cassette playback
109
+ - Helpers for recording batch operations
110
+ - Support for error recording and playback
111
+ - Performance tracking across recordings
112
+
113
+ **Note**: To use VCR integration, add these gems to your Gemfile:
114
+ ```ruby
115
+ group :development, :test do
116
+ gem 'vcr', '~> 6.0'
117
+ gem 'webmock', '~> 3.0'
118
+ end
119
+ ```
120
+
121
+ Example usage:
122
+ ```ruby
123
+ with_graphql_vcr('api_calls') do
124
+ result = executor.execute(graphql_query)
125
+ assert_graphql_success(result)
126
+ end
127
+ ```
128
+
129
+ Benefits:
130
+ - Reproducible tests without hitting real APIs
131
+ - Faster test execution with cassette playback
132
+ - Easy debugging with recorded interactions
133
+ - Consistent test results across environments
134
+
135
+ ## Future Optimizations
136
+
137
+ Additional optimizations that could be implemented:
138
+ 1. Smarter cache key generation using content hashing
139
+ 2. Connection pooling for parallel batch processing
140
+ 3. Adaptive batch sizing based on load patterns
141
+ 4. Request prioritization for critical queries
142
+ 5. Metrics collection for monitoring deduplication effectiveness
143
+ 6. Integration with APM tools for performance tracking