funapi 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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/25-09-01-OPENAPI_IMPLEMENTATION.md +233 -0
  3. data/.claude/25-09-05-RESPONSE_SCHEMA.md +383 -0
  4. data/.claude/25-09-10-OPENAPI_PLAN.md +219 -0
  5. data/.claude/25-10-26-MIDDLEWARE_IMPLEMENTATION.md +230 -0
  6. data/.claude/25-10-26-MIDDLEWARE_PLAN.md +353 -0
  7. data/.claude/25-10-27-BACKGROUND_TASKS_ANALYSIS.md +325 -0
  8. data/.claude/25-10-27-DEPENDENCY_IMPLEMENTATION_SUMMARY.md +325 -0
  9. data/.claude/25-10-27-DEPENDENCY_INJECTION_PLAN.md +753 -0
  10. data/.claude/25-12-24-LIFECYCLE_HOOKS_PLAN.md +421 -0
  11. data/.claude/25-12-24-PUBLISHING_AND_DOGFOODING_PLAN.md +327 -0
  12. data/.claude/25-12-24-TEMPLATE_RENDERING_PLAN.md +704 -0
  13. data/.claude/DECISIONS.md +397 -0
  14. data/.claude/PROJECT_PLAN.md +80 -0
  15. data/.claude/TESTING_PLAN.md +285 -0
  16. data/.claude/TESTING_STATUS.md +157 -0
  17. data/.tool-versions +1 -0
  18. data/AGENTS.md +416 -0
  19. data/CHANGELOG.md +5 -0
  20. data/CODE_OF_CONDUCT.md +132 -0
  21. data/LICENSE.txt +21 -0
  22. data/README.md +660 -0
  23. data/Rakefile +10 -0
  24. data/docs +8 -0
  25. data/docs-site/.gitignore +3 -0
  26. data/docs-site/Gemfile +9 -0
  27. data/docs-site/app.rb +138 -0
  28. data/docs-site/content/essential/handler.md +156 -0
  29. data/docs-site/content/essential/lifecycle.md +161 -0
  30. data/docs-site/content/essential/middleware.md +201 -0
  31. data/docs-site/content/essential/openapi.md +155 -0
  32. data/docs-site/content/essential/routing.md +123 -0
  33. data/docs-site/content/essential/validation.md +166 -0
  34. data/docs-site/content/getting-started/at-glance.md +82 -0
  35. data/docs-site/content/getting-started/key-concepts.md +150 -0
  36. data/docs-site/content/getting-started/quick-start.md +127 -0
  37. data/docs-site/content/index.md +81 -0
  38. data/docs-site/content/patterns/async-operations.md +137 -0
  39. data/docs-site/content/patterns/background-tasks.md +143 -0
  40. data/docs-site/content/patterns/database.md +175 -0
  41. data/docs-site/content/patterns/dependencies.md +141 -0
  42. data/docs-site/content/patterns/deployment.md +212 -0
  43. data/docs-site/content/patterns/error-handling.md +184 -0
  44. data/docs-site/content/patterns/response-schema.md +159 -0
  45. data/docs-site/content/patterns/templates.md +193 -0
  46. data/docs-site/content/patterns/testing.md +218 -0
  47. data/docs-site/mise.toml +2 -0
  48. data/docs-site/public/css/style.css +234 -0
  49. data/docs-site/templates/layouts/docs.html.erb +28 -0
  50. data/docs-site/templates/page.html.erb +3 -0
  51. data/docs-site/templates/partials/_nav.html.erb +19 -0
  52. data/examples/background_tasks_demo.rb +159 -0
  53. data/examples/demo_middleware.rb +55 -0
  54. data/examples/demo_openapi.rb +63 -0
  55. data/examples/dependency_block_demo.rb +150 -0
  56. data/examples/dependency_cleanup_demo.rb +146 -0
  57. data/examples/dependency_injection_demo.rb +200 -0
  58. data/examples/lifecycle_demo.rb +57 -0
  59. data/examples/middleware_demo.rb +74 -0
  60. data/examples/templates/layouts/application.html.erb +66 -0
  61. data/examples/templates/todos/_todo.html.erb +15 -0
  62. data/examples/templates/todos/index.html.erb +12 -0
  63. data/examples/templates_demo.rb +87 -0
  64. data/lib/funapi/application.rb +521 -0
  65. data/lib/funapi/async.rb +57 -0
  66. data/lib/funapi/background_tasks.rb +52 -0
  67. data/lib/funapi/config.rb +23 -0
  68. data/lib/funapi/database/sequel/fibered_connection_pool.rb +87 -0
  69. data/lib/funapi/dependency_wrapper.rb +66 -0
  70. data/lib/funapi/depends.rb +138 -0
  71. data/lib/funapi/exceptions.rb +72 -0
  72. data/lib/funapi/middleware/base.rb +13 -0
  73. data/lib/funapi/middleware/cors.rb +23 -0
  74. data/lib/funapi/middleware/request_logger.rb +32 -0
  75. data/lib/funapi/middleware/trusted_host.rb +34 -0
  76. data/lib/funapi/middleware.rb +4 -0
  77. data/lib/funapi/openapi/schema_converter.rb +85 -0
  78. data/lib/funapi/openapi/spec_generator.rb +179 -0
  79. data/lib/funapi/router.rb +43 -0
  80. data/lib/funapi/schema.rb +65 -0
  81. data/lib/funapi/server/falcon.rb +38 -0
  82. data/lib/funapi/template_response.rb +17 -0
  83. data/lib/funapi/templates.rb +111 -0
  84. data/lib/funapi/version.rb +5 -0
  85. data/lib/funapi.rb +14 -0
  86. data/sig/fun_api.rbs +499 -0
  87. metadata +220 -0
@@ -0,0 +1,325 @@
1
+ # Dependency Injection Implementation Summary
2
+
3
+ ## Date: 2024-10-27
4
+
5
+ ## What We Built
6
+
7
+ A complete FastAPI-inspired dependency injection system for FunApi with a **Ruby-idiomatic block-based cleanup pattern**.
8
+
9
+ ## Key Features Implemented
10
+
11
+ ### 1. Dependency Registration with Cleanup
12
+
13
+ **Three supported patterns:**
14
+
15
+ ```ruby
16
+ # 1. Simple (no cleanup)
17
+ api.register(:config) { Config.load }
18
+
19
+ # 2. Tuple pattern (backward compatible)
20
+ api.register(:cache) do
21
+ [Cache.new, -> { Cache.shutdown }]
22
+ end
23
+
24
+ # 3. Block with ensure (RECOMMENDED - most Ruby-like)
25
+ api.register(:db) do |provide|
26
+ conn = Database.connect
27
+ provide.call(conn)
28
+ ensure
29
+ conn.close
30
+ end
31
+ ```
32
+
33
+ ### 2. Request-Scoped Caching
34
+
35
+ Dependencies resolved once per request, even if referenced multiple times:
36
+
37
+ ```ruby
38
+ api.get '/dashboard',
39
+ depends: { db1: :db, db2: :db } do |input, req, task, db1:, db2:|
40
+ # db1 and db2 are the SAME instance
41
+ # Connection opened once, closed once
42
+ end
43
+ ```
44
+
45
+ ### 3. Automatic Cleanup
46
+
47
+ Cleanup runs in `ensure` block:
48
+ - ✅ Always runs after response sent
49
+ - ✅ Runs even if handler raises error
50
+ - ✅ Runs even if earlier cleanup fails
51
+ - ✅ Failures logged but don't break response
52
+
53
+ ### 4. Nested Dependencies
54
+
55
+ ```ruby
56
+ api.register(:db) { |provide| ... }
57
+
58
+ def get_current_user
59
+ ->(req:, db:) { authenticate(req, db) }
60
+ end
61
+
62
+ api.get '/profile',
63
+ depends: { user: FunApi::Depends(get_current_user, db: :db) }
64
+ ```
65
+
66
+ ### 5. Multiple Dependency Styles
67
+
68
+ ```ruby
69
+ # Array syntax
70
+ api.get '/test', depends: [:db, :cache]
71
+
72
+ # Hash with symbols
73
+ api.get '/test', depends: { db: nil, cache: nil }
74
+
75
+ # Hash with inline lambdas
76
+ api.get '/test', depends: { value: -> { 42 } }
77
+
78
+ # Hash with callable classes
79
+ api.get '/test', depends: { page: Paginator.new(max: 50) }
80
+ ```
81
+
82
+ ## Architecture
83
+
84
+ ### Component Structure
85
+
86
+ ```
87
+ lib/fun_api/
88
+ ├── depends.rb # Depends wrapper for nested deps
89
+ ├── dependency_wrapper.rb # Three wrapper types
90
+ │ ├── SimpleDependency # No cleanup
91
+ │ ├── ManagedDependency # Tuple pattern
92
+ │ └── BlockDependency # Block with ensure (Fiber-based)
93
+ └── application.rb # Container & resolution logic
94
+ ```
95
+
96
+ ### Fiber-Based Lifecycle
97
+
98
+ `BlockDependency` uses Ruby Fiber for resource lifecycle:
99
+
100
+ ```ruby
101
+ class BlockDependency
102
+ def call
103
+ @fiber = Fiber.new do
104
+ @block.call(proc { |resource|
105
+ Fiber.yield resource # Provide to handler
106
+ })
107
+ end
108
+ @resource = @fiber.resume # Get resource
109
+ end
110
+
111
+ def cleanup
112
+ @fiber.resume if @fiber.alive? # Trigger ensure block
113
+ end
114
+ end
115
+ ```
116
+
117
+ ### Resolution Flow
118
+
119
+ 1. Route handler matched
120
+ 2. Dependencies normalized (detect pattern type)
121
+ 3. Dependencies resolved (with caching)
122
+ 4. Handler called with injected kwargs
123
+ 5. Response prepared
124
+ 6. **Cleanup runs in ensure block**
125
+
126
+ ## Test Coverage
127
+
128
+ **121 tests, 281 assertions, all passing**
129
+
130
+ Test files:
131
+ - `test/test_depends.rb` - Depends class unit tests (12 tests)
132
+ - `test/test_dependency_injection.rb` - Integration tests (12 tests)
133
+ - `test/test_dependency_cleanup.rb` - Cleanup behavior (7 tests)
134
+ - All existing tests still pass (90 tests)
135
+
136
+ ## FastAPI Alignment
137
+
138
+ Our pattern matches FastAPI's dependencies with yield:
139
+
140
+ **Python:**
141
+ ```python
142
+ def get_db():
143
+ db = SessionLocal()
144
+ try:
145
+ yield db
146
+ finally:
147
+ db.close()
148
+
149
+ @app.get("/users")
150
+ def read_users(db: Session = Depends(get_db)):
151
+ return db.query(User).all()
152
+ ```
153
+
154
+ **Ruby:**
155
+ ```ruby
156
+ api.register(:db) do |provide|
157
+ db = Database.connect
158
+ provide.call(db)
159
+ ensure
160
+ db.close
161
+ end
162
+
163
+ api.get '/users', depends: [:db] do |input, req, task, db:|
164
+ [db.all_users, 200]
165
+ end
166
+ ```
167
+
168
+ ## Design Decisions
169
+
170
+ ### Why Block-Based Pattern?
171
+
172
+ 1. **Most idiomatic Ruby** - Matches patterns like:
173
+ - `File.open { |f| ... }`
174
+ - `Dir.chdir { ... }`
175
+ - `Mutex.synchronize { ... }`
176
+
177
+ 2. **Safety** - `ensure` guarantees cleanup runs
178
+
179
+ 3. **Can't forget** - Cleanup is built into the pattern
180
+
181
+ 4. **Explicit lifecycle** - Clear setup → use → cleanup flow
182
+
183
+ 5. **FastAPI parity** - Same concept as context managers
184
+
185
+ ### Why Not Dry-System/Dry-Effects?
186
+
187
+ - Too heavy for FunApi's minimal philosophy
188
+ - Constructor injection vs parameter injection mismatch
189
+ - Adds significant complexity
190
+ - Our custom solution is simpler and more aligned
191
+
192
+ ### Tuple Pattern Still Supported
193
+
194
+ For backward compatibility and simple cases:
195
+ ```ruby
196
+ api.register(:cache) { [Cache.new, -> { Cache.shutdown }] }
197
+ ```
198
+
199
+ ## Examples
200
+
201
+ Created three comprehensive examples:
202
+ 1. `examples/dependency_injection_demo.rb` - Full auth example with nested deps
203
+ 2. `examples/dependency_cleanup_demo.rb` - Shows cleanup lifecycle (tuple pattern)
204
+ 3. `examples/dependency_block_demo.rb` - Shows new block pattern (RECOMMENDED)
205
+
206
+ ## Usage Patterns
207
+
208
+ ### Database Connection
209
+
210
+ ```ruby
211
+ api.register(:db) do |provide|
212
+ conn = Database.connect
213
+ puts "✅ Connection opened"
214
+ provide.call(conn)
215
+ ensure
216
+ conn&.close
217
+ puts "❌ Connection closed"
218
+ end
219
+ ```
220
+
221
+ ### File Handles
222
+
223
+ ```ruby
224
+ api.register(:log_file) do |provide|
225
+ file = File.open('app.log', 'a')
226
+ provide.call(file)
227
+ ensure
228
+ file&.close
229
+ end
230
+ ```
231
+
232
+ ### Transactions
233
+
234
+ ```ruby
235
+ api.register(:transaction) do |provide|
236
+ tx = db.begin_transaction
237
+ begin
238
+ provide.call(tx)
239
+ tx.commit
240
+ rescue
241
+ tx.rollback
242
+ raise
243
+ ensure
244
+ tx.close
245
+ end
246
+ end
247
+ ```
248
+
249
+ ### Authentication (Nested)
250
+
251
+ ```ruby
252
+ api.register(:db) { |provide| ... }
253
+
254
+ def require_auth
255
+ ->(req:, db:) {
256
+ token = req.env['HTTP_AUTHORIZATION']
257
+ user = db.find_user_by_token(token)
258
+ raise HTTPException.new(status_code: 401) unless user
259
+ user
260
+ }
261
+ end
262
+
263
+ api.get '/profile',
264
+ depends: { user: FunApi::Depends(require_auth, db: :db) }
265
+ ```
266
+
267
+ ## Performance Considerations
268
+
269
+ - **Request-scoped caching** - Dependencies resolved once per request
270
+ - **No overhead without deps** - Routes without dependencies have no DI overhead
271
+ - **Fiber cost** - Minimal (Ruby Fibers are lightweight)
272
+ - **Cleanup always runs** - Even on errors, no resource leaks
273
+
274
+ ## Future Enhancements
275
+
276
+ Could add later (not needed now):
277
+ - Global dependencies (apply to all routes)
278
+ - Dependency overrides for testing
279
+ - Async context managers (for async resources)
280
+ - Dependency graph visualization
281
+
282
+ ## Migration Guide
283
+
284
+ For users upgrading from tuple pattern:
285
+
286
+ **Before:**
287
+ ```ruby
288
+ api.register(:db) do
289
+ conn = Database.connect
290
+ [conn, -> { conn.close }]
291
+ end
292
+ ```
293
+
294
+ **After (recommended):**
295
+ ```ruby
296
+ api.register(:db) do |provide|
297
+ conn = Database.connect
298
+ provide.call(conn)
299
+ ensure
300
+ conn.close
301
+ end
302
+ ```
303
+
304
+ Both patterns work! The block pattern is recommended for better Ruby idioms.
305
+
306
+ ## Success Metrics
307
+
308
+ ✅ All 121 tests passing
309
+ ✅ Zero regressions
310
+ ✅ FastAPI parity achieved
311
+ ✅ Ruby-idiomatic API
312
+ ✅ Production-ready
313
+
314
+ ## References
315
+
316
+ - FastAPI Dependencies: https://fastapi.tiangolo.com/tutorial/dependencies/
317
+ - FastAPI with yield: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/
318
+ - Ruby Fiber documentation
319
+ - dry-rb ecosystem review
320
+
321
+ ---
322
+
323
+ **Status:** ✅ Complete and production-ready
324
+ **Date:** October 27, 2024
325
+ **Tests:** 121 passing, 281 assertions