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,397 @@
1
+ # FunApi - Architectural Decisions
2
+
3
+ This document records key architectural and design decisions made during the development of FunApi.
4
+
5
+ ## Testing Strategy (2024-10-26)
6
+
7
+ ### Decision: Flat Test Structure
8
+
9
+ **Context**: Needed to organize tests for the framework. Options were nested directories (unit/, integration/) vs flat structure.
10
+
11
+ **Decision**: Use flat test structure following Sidekiq's pattern.
12
+
13
+ **Rationale**:
14
+ - Simplicity - easier to find tests
15
+ - Flexibility - tests can be unit or integration as needed
16
+ - No artificial boundaries - test what matters, not how
17
+ - Proven pattern - Sidekiq has excellent test organization
18
+ - Library-friendly - FunApi is a library, not an application
19
+
20
+ **Implementation**:
21
+ ```
22
+ test/
23
+ ├── test_helper.rb
24
+ ├── test_fun_api.rb # Basic smoke tests
25
+ ├── test_router.rb # Router functionality
26
+ ├── test_schema.rb # Validation
27
+ ├── test_middleware.rb # Middleware chain
28
+ ├── test_validation.rb # Request validation
29
+ ├── test_response_schema.rb
30
+ ├── test_async.rb
31
+ └── test_exceptions.rb
32
+ ```
33
+
34
+ **Result**: 90 tests, 217 assertions, all passing in ~220ms
35
+
36
+ ---
37
+
38
+ ## Middleware System (2024-10-26)
39
+
40
+ ### Decision: Rack-Compatible Middleware with FastAPI-Style Convenience
41
+
42
+ **Context**: Needed middleware support. Options: Build custom system vs leverage Rack ecosystem.
43
+
44
+ **Decision**: Support standard Rack middleware PLUS provide FastAPI-style convenience methods.
45
+
46
+ **Rationale**:
47
+ - Leverage battle-tested Rack ecosystem (15+ years, 100+ middleware)
48
+ - No reinvention - delegate to proven libraries (rack-cors, Rack::Deflater)
49
+ - FastAPI-like DX - convenience methods for common use cases
50
+ - Zero lock-in - users can use any Rack middleware
51
+ - Async compatible - Rack 3.0+ supports async natively
52
+
53
+ **Implementation**:
54
+ ```ruby
55
+ # Standard Rack middleware
56
+ app.use Rack::Attack
57
+ app.use Rack::Session::Cookie, secret: 'key'
58
+
59
+ # FunApi convenience methods
60
+ app.add_cors(allow_origins: ['*'])
61
+ app.add_trusted_host(allowed_hosts: ['example.com'])
62
+ app.add_request_logger
63
+ ```
64
+
65
+ **Built-in Middleware**:
66
+ - CORS (wraps rack-cors)
67
+ - TrustedHost (custom implementation)
68
+ - RequestLogger (custom with async awareness)
69
+ - Gzip (delegates to Rack::Deflater)
70
+
71
+ **Result**: Full Rack compatibility + excellent developer experience
72
+
73
+ ---
74
+
75
+ ## Middleware Execution Order (2024-10-26)
76
+
77
+ ### Decision: Standard Rack Ordering (LIFO wrapping, FIFO execution)
78
+
79
+ **Context**: How should middleware execute when multiple are registered?
80
+
81
+ **Decision**: First registered middleware runs first (outermost layer).
82
+
83
+ **Rationale**:
84
+ - Standard Ruby/Rack convention
85
+ - Expected by Rack developers
86
+ - Well-documented behavior
87
+ - Works with all existing Rack middleware
88
+
89
+ **Example**:
90
+ ```ruby
91
+ app.use Middleware1 # Executes FIRST
92
+ app.use Middleware2 # Executes SECOND
93
+ # Router executes LAST
94
+ ```
95
+
96
+ Request flow: MW1 → MW2 → Router → MW2 → MW1
97
+
98
+ ---
99
+
100
+ ## OpenAPI Implementation (2024-09)
101
+
102
+ ### Decision: Automatic Schema Extraction
103
+
104
+ **Context**: How to generate OpenAPI specs from dry-schema definitions?
105
+
106
+ **Decision**: Introspect dry-schema at runtime and convert to JSON Schema.
107
+
108
+ **Rationale**:
109
+ - No manual duplication
110
+ - Single source of truth (the schema)
111
+ - Automatic documentation updates
112
+ - FastAPI-like experience
113
+
114
+ **Implementation**:
115
+ - SchemaConverter: dry-schema → JSON Schema
116
+ - SpecGenerator: routes + schemas → OpenAPI 3.0.3 spec
117
+ - Auto-register /openapi.json and /docs endpoints
118
+
119
+ ---
120
+
121
+ ## Response Schema Filtering (2024-09)
122
+
123
+ ### Decision: Security-First Response Filtering
124
+
125
+ **Context**: How to handle sensitive data in responses (passwords, tokens)?
126
+
127
+ **Decision**: Response schemas filter output to only include specified fields.
128
+
129
+ **Rationale**:
130
+ - Security by default
131
+ - Prevent accidental data leaks
132
+ - FastAPI's response_model pattern
133
+ - Explicit is better than implicit
134
+
135
+ **Example**:
136
+ ```ruby
137
+ UserOutputSchema = FunApi::Schema.define do
138
+ required(:id).filled(:integer)
139
+ required(:name).filled(:string)
140
+ # password NOT included
141
+ end
142
+
143
+ api.get '/user', response_schema: UserOutputSchema do
144
+ user = { id: 1, name: 'Alice', password: 'secret' }
145
+ [user, 200] # Password automatically filtered
146
+ end
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Async-First Design (2024-09)
152
+
153
+ ### Decision: Async::Task as Third Handler Parameter
154
+
155
+ **Context**: How to expose async capabilities to route handlers?
156
+
157
+ **Decision**: Pass `Async::Task` as third parameter to all handlers.
158
+
159
+ **Rationale**:
160
+ - Explicit async access
161
+ - No magic globals
162
+ - True concurrent execution
163
+ - Ruby's Async library is mature
164
+
165
+ **Example**:
166
+ ```ruby
167
+ api.get '/dashboard' do |input, req, task|
168
+ user_task = task.async { fetch_user }
169
+ posts_task = task.async { fetch_posts }
170
+
171
+ [{ user: user_task.wait, posts: posts_task.wait }, 200]
172
+ end
173
+ ```
174
+
175
+ **Trade-off**: Three parameters instead of two, but explicitness wins.
176
+
177
+ ---
178
+
179
+ ## Router Root Route Fix (2024-10-26)
180
+
181
+ ### Decision: Special-Case Root Route `/`
182
+
183
+ **Context**: Regex generation failed for `/` route (empty regex).
184
+
185
+ **Decision**: Explicitly handle `/` as special case before regex generation.
186
+
187
+ **Rationale**:
188
+ - `/` is common and important
189
+ - Regex `/\A\z/` doesn't match `/`
190
+ - Simple fix with no overhead
191
+ - Prevents future bugs
192
+
193
+ **Implementation**:
194
+ ```ruby
195
+ if path == '/'
196
+ regex = '/'
197
+ else
198
+ # normal regex generation
199
+ end
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Validation Error Format (2024-09)
205
+
206
+ ### Decision: FastAPI-Compatible Error Format
207
+
208
+ **Context**: How to structure validation errors?
209
+
210
+ **Decision**: Use FastAPI's error format with `detail` array.
211
+
212
+ **Rationale**:
213
+ - Familiar to FastAPI users
214
+ - Structured and parseable
215
+ - Clear error location information
216
+ - Industry standard
217
+
218
+ **Format**:
219
+ ```json
220
+ {
221
+ "detail": [
222
+ {
223
+ "loc": ["body", "email"],
224
+ "msg": "is missing",
225
+ "type": "value_error"
226
+ }
227
+ ]
228
+ }
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Schema Validation (2024-09)
234
+
235
+ ### Decision: dry-schema Over ActiveModel
236
+
237
+ **Context**: Which validation library to use?
238
+
239
+ **Decision**: Use dry-schema for validation.
240
+
241
+ **Rationale**:
242
+ - Lightweight (no Rails dependency)
243
+ - Functional approach (no mutations)
244
+ - Better API error messages
245
+ - Type coercion built-in
246
+ - Fast and battle-tested
247
+
248
+ **Trade-off**: Less familiar to Rails developers, but better fit for APIs.
249
+
250
+ ---
251
+
252
+ ## Server Choice (2024-09)
253
+
254
+ ### Decision: Falcon as Default Server
255
+
256
+ **Context**: Which Rack server to recommend?
257
+
258
+ **Decision**: Falcon for development and production.
259
+
260
+ **Rationale**:
261
+ - Native async support
262
+ - Built for Async library
263
+ - Better concurrency for I/O-bound work
264
+ - Aligns with async-first philosophy
265
+
266
+ **Note**: Any Rack 3+ server will work (Puma, Unicorn, etc.)
267
+
268
+ ---
269
+
270
+ ## Documentation Strategy (2024-10-26)
271
+
272
+ ### Decision: Dual Documentation (README + AGENTS.md)
273
+
274
+ **Context**: How to document for both humans and AI agents?
275
+
276
+ **Decision**:
277
+ - README.md for human users (features, examples)
278
+ - AGENTS.md for AI coding agents (architecture, testing, conventions)
279
+
280
+ **Rationale**:
281
+ - Following agents.md standard
282
+ - Different audiences need different info
283
+ - Keeps README concise
284
+ - Provides deep context for agents
285
+
286
+ ---
287
+
288
+ ## Examples as Documentation (2024-10-26)
289
+
290
+ ### Decision: Executable Examples > Test Assertions
291
+
292
+ **Context**: Should examples be automated tests?
293
+
294
+ **Decision**: Keep examples simple and executable, separate from test suite.
295
+
296
+ **Rationale**:
297
+ - Examples show real usage
298
+ - Can be run manually for smoke testing
299
+ - Don't clutter with assertions
300
+ - Living documentation
301
+ - Test suite covers thorough testing
302
+
303
+ **Organization**:
304
+ - `examples/` - Runnable demos
305
+ - `test/` - Automated tests
306
+ - `test/demo_*.rb` - Reference demos (not automated)
307
+
308
+ ---
309
+
310
+ ## Dependency Injection (2024-10-27)
311
+
312
+ ### Decision: Block-Based Dependencies with Cleanup
313
+
314
+ **Context**: Need DI with automatic resource cleanup. Evaluated: dry-system, tuples, context managers, blocks.
315
+
316
+ **Decision**: Ruby blocks with `ensure` for lifecycle management.
317
+
318
+ **Rationale**:
319
+ - Most idiomatic Ruby (matches `File.open`, `Mutex.synchronize`)
320
+ - `ensure` guarantees cleanup, even on errors
321
+ - FastAPI parity (context managers)
322
+ - Lightweight (uses Fiber, no heavy deps)
323
+
324
+ **Pattern**:
325
+ ```ruby
326
+ api.register(:db) do |provide|
327
+ conn = Database.connect
328
+ provide.call(conn)
329
+ ensure
330
+ conn.close
331
+ end
332
+
333
+ api.get '/users', depends: [:db] do |input, req, task, db:|
334
+ [db.all_users, 200]
335
+ end
336
+ ```
337
+
338
+ **Features**:
339
+ - Request-scoped caching
340
+ - Nested dependencies with `FunApi::Depends()`
341
+ - Three patterns: simple, tuple (compat), block (preferred)
342
+ - Cleanup in `ensure` after response sent
343
+
344
+ **Result**: 121 tests passing, FastAPI-aligned.
345
+
346
+ ---
347
+
348
+ ## Dependency Injection: Not dry-system (2024-10-27)
349
+
350
+ ### Decision: Custom Lightweight DI
351
+
352
+ **Context**: dry-system exists but designed for different use case.
353
+
354
+ **Decision**: Build custom DI using only dry-container.
355
+
356
+ **Rationale**:
357
+ - dry-system = constructor injection, we need parameter injection
358
+ - FunApi is minimal, dry-system is comprehensive
359
+ - Custom solution maps 1:1 to FastAPI's `Depends()`
360
+
361
+ **Used**: dry-container (registry only)
362
+ **Skipped**: dry-system, dry-auto_inject, dry-effects
363
+
364
+ ---
365
+
366
+ ## Future Decisions Pending
367
+
368
+ 1. ~~**Dependency Injection**~~ ✅ Done
369
+ 2. **Background Tasks**: Post-response execution
370
+ 3. **Path Parameter Types**: Type coercion/validation
371
+ 4. **WebSocket Support**: Async integration
372
+ 5. **Content Negotiation**: JSON + others
373
+ 6. **Global Dependencies**: Apply to all routes
374
+
375
+ ---
376
+
377
+ ## Non-Decisions (Explicitly Rejected)
378
+
379
+ ### Rails Integration
380
+ **Decision**: FunApi remains independent of Rails.
381
+ **Reason**: Keep it minimal, different use case.
382
+
383
+ ### Magic DSLs
384
+ **Decision**: No heavy DSLs or metaprogramming.
385
+ **Reason**: Explicit is better than implicit.
386
+
387
+ ### Database Integration
388
+ **Decision**: No built-in ORM or database layer.
389
+ **Reason**: Users choose their own (Sequel, ROM, ActiveRecord).
390
+
391
+ ---
392
+
393
+ ## Change Log
394
+
395
+ - 2024-10-27: Added dependency injection decisions
396
+ - 2024-10-26: Testing, middleware, documentation strategies
397
+ - 2024-09: Initial core framework decisions
@@ -0,0 +1,80 @@
1
+ # Project Overview
2
+
3
+ A minimal, async-first Ruby web framework inspired by FastAPI. Built on top of Falcon and dry-schema, FunApi provides a simple, performant way to build web APIs in Ruby with a focus on developer experience.
4
+
5
+ ## Philosophy
6
+
7
+ FunApi aims to bring FastAPI's excellent developer experience to Ruby by providing:
8
+
9
+ - **Async-first**: Built on Ruby's Async library and Falcon server for high-performance concurrent operations
10
+ - **Simple validation**: Using dry-schema for straightforward request validation
11
+ - **Minimal magic**: Clear, explicit APIs without heavy DSLs
12
+ - **Easy to start**: Get an API up and running in minutes
13
+ - **Auto-documentation**: Automatic OpenAPI/Swagger documentation generation
14
+
15
+ ## Current Status (Updated 2024-10-27)
16
+
17
+ ### ✅ Production-Ready Features Implemented:
18
+
19
+ **Core Framework**
20
+ - ✅ Async-first request handling with Async::Task
21
+ - ✅ Route definition with path params
22
+ - ✅ Request validation (body/query) with array support
23
+ - ✅ Response schema validation and filtering
24
+ - ✅ FastAPI-style error responses
25
+ - ✅ Falcon server integration
26
+
27
+ **Dependency Injection** ⭐ NEW
28
+ - ✅ Block-based dependencies with `ensure` cleanup
29
+ - ✅ Request-scoped caching
30
+ - ✅ Nested dependencies with `FunApi::Depends()`
31
+ - ✅ Three patterns: simple, tuple, block (Ruby-idiomatic)
32
+ - ✅ Automatic resource lifecycle (setup → use → cleanup)
33
+ - ✅ FastAPI `Depends()` parity
34
+
35
+ **Documentation**
36
+ - ✅ OpenAPI/Swagger documentation generation
37
+ - ✅ Automatic /docs and /openapi.json endpoints
38
+ - ✅ Schema introspection and conversion
39
+
40
+ **Middleware**
41
+ - ✅ Rack-compatible middleware system
42
+ - ✅ Built-in middleware (CORS, TrustedHost, RequestLogger, Gzip)
43
+ - ✅ FastAPI-style convenience methods
44
+ - ✅ Full ecosystem support (rack-attack, rack-cache, etc.)
45
+
46
+ **Testing**
47
+ - ✅ Comprehensive test suite (121 tests, 281 assertions)
48
+ - ✅ Router, schema, middleware, async, exceptions
49
+ - ✅ Dependency injection (31 new tests)
50
+ - ✅ Fast execution (~200ms)
51
+
52
+ ### 📋 What to Tackle Next
53
+
54
+ ### Immediate Priority
55
+
56
+ 1. **Background Tasks** - Post-response execution
57
+ - Fire-and-forget after response sent
58
+ - Email, logging, cleanup tasks
59
+ - Leverages async foundation
60
+ - Why: Common production pattern, natural async fit
61
+
62
+ 2. **Lifecycle Hooks** - Startup/shutdown
63
+ - App initialization/cleanup
64
+ - Exception handlers
65
+ - Why: Production deployment needs
66
+
67
+ ### Secondary Priority
68
+
69
+ 3. **Path Parameter Types** - Type coercion/validation
70
+ 4. **File Uploads** - Multipart handling
71
+ 5. **WebSocket Support** - Real-time connections
72
+ 6. **Global Dependencies** - Apply to all routes
73
+ 7. **Dependency Overrides** - Testing utilities
74
+
75
+ ### Nice to Have
76
+
77
+ - Content negotiation (JSON, XML, MessagePack)
78
+ - TestClient utilities
79
+ - Security schemes (OAuth2, JWT helpers)
80
+ - Response streaming