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.
- checksums.yaml +7 -0
- data/.claude/25-09-01-OPENAPI_IMPLEMENTATION.md +233 -0
- data/.claude/25-09-05-RESPONSE_SCHEMA.md +383 -0
- data/.claude/25-09-10-OPENAPI_PLAN.md +219 -0
- data/.claude/25-10-26-MIDDLEWARE_IMPLEMENTATION.md +230 -0
- data/.claude/25-10-26-MIDDLEWARE_PLAN.md +353 -0
- data/.claude/25-10-27-BACKGROUND_TASKS_ANALYSIS.md +325 -0
- data/.claude/25-10-27-DEPENDENCY_IMPLEMENTATION_SUMMARY.md +325 -0
- data/.claude/25-10-27-DEPENDENCY_INJECTION_PLAN.md +753 -0
- data/.claude/25-12-24-LIFECYCLE_HOOKS_PLAN.md +421 -0
- data/.claude/25-12-24-PUBLISHING_AND_DOGFOODING_PLAN.md +327 -0
- data/.claude/25-12-24-TEMPLATE_RENDERING_PLAN.md +704 -0
- data/.claude/DECISIONS.md +397 -0
- data/.claude/PROJECT_PLAN.md +80 -0
- data/.claude/TESTING_PLAN.md +285 -0
- data/.claude/TESTING_STATUS.md +157 -0
- data/.tool-versions +1 -0
- data/AGENTS.md +416 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +660 -0
- data/Rakefile +10 -0
- data/docs +8 -0
- data/docs-site/.gitignore +3 -0
- data/docs-site/Gemfile +9 -0
- data/docs-site/app.rb +138 -0
- data/docs-site/content/essential/handler.md +156 -0
- data/docs-site/content/essential/lifecycle.md +161 -0
- data/docs-site/content/essential/middleware.md +201 -0
- data/docs-site/content/essential/openapi.md +155 -0
- data/docs-site/content/essential/routing.md +123 -0
- data/docs-site/content/essential/validation.md +166 -0
- data/docs-site/content/getting-started/at-glance.md +82 -0
- data/docs-site/content/getting-started/key-concepts.md +150 -0
- data/docs-site/content/getting-started/quick-start.md +127 -0
- data/docs-site/content/index.md +81 -0
- data/docs-site/content/patterns/async-operations.md +137 -0
- data/docs-site/content/patterns/background-tasks.md +143 -0
- data/docs-site/content/patterns/database.md +175 -0
- data/docs-site/content/patterns/dependencies.md +141 -0
- data/docs-site/content/patterns/deployment.md +212 -0
- data/docs-site/content/patterns/error-handling.md +184 -0
- data/docs-site/content/patterns/response-schema.md +159 -0
- data/docs-site/content/patterns/templates.md +193 -0
- data/docs-site/content/patterns/testing.md +218 -0
- data/docs-site/mise.toml +2 -0
- data/docs-site/public/css/style.css +234 -0
- data/docs-site/templates/layouts/docs.html.erb +28 -0
- data/docs-site/templates/page.html.erb +3 -0
- data/docs-site/templates/partials/_nav.html.erb +19 -0
- data/examples/background_tasks_demo.rb +159 -0
- data/examples/demo_middleware.rb +55 -0
- data/examples/demo_openapi.rb +63 -0
- data/examples/dependency_block_demo.rb +150 -0
- data/examples/dependency_cleanup_demo.rb +146 -0
- data/examples/dependency_injection_demo.rb +200 -0
- data/examples/lifecycle_demo.rb +57 -0
- data/examples/middleware_demo.rb +74 -0
- data/examples/templates/layouts/application.html.erb +66 -0
- data/examples/templates/todos/_todo.html.erb +15 -0
- data/examples/templates/todos/index.html.erb +12 -0
- data/examples/templates_demo.rb +87 -0
- data/lib/funapi/application.rb +521 -0
- data/lib/funapi/async.rb +57 -0
- data/lib/funapi/background_tasks.rb +52 -0
- data/lib/funapi/config.rb +23 -0
- data/lib/funapi/database/sequel/fibered_connection_pool.rb +87 -0
- data/lib/funapi/dependency_wrapper.rb +66 -0
- data/lib/funapi/depends.rb +138 -0
- data/lib/funapi/exceptions.rb +72 -0
- data/lib/funapi/middleware/base.rb +13 -0
- data/lib/funapi/middleware/cors.rb +23 -0
- data/lib/funapi/middleware/request_logger.rb +32 -0
- data/lib/funapi/middleware/trusted_host.rb +34 -0
- data/lib/funapi/middleware.rb +4 -0
- data/lib/funapi/openapi/schema_converter.rb +85 -0
- data/lib/funapi/openapi/spec_generator.rb +179 -0
- data/lib/funapi/router.rb +43 -0
- data/lib/funapi/schema.rb +65 -0
- data/lib/funapi/server/falcon.rb +38 -0
- data/lib/funapi/template_response.rb +17 -0
- data/lib/funapi/templates.rb +111 -0
- data/lib/funapi/version.rb +5 -0
- data/lib/funapi.rb +14 -0
- data/sig/fun_api.rbs +499 -0
- metadata +220 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# Lifecycle Hooks Implementation Plan
|
|
2
|
+
|
|
3
|
+
## Date: 2024-12-24
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Implement startup and shutdown lifecycle hooks for FunApi. These hooks allow users to run code before the server accepts requests and after it stops, useful for initializing/cleaning up resources like database connections, HTTP clients, and background task supervisors.
|
|
8
|
+
|
|
9
|
+
## Design Decision
|
|
10
|
+
|
|
11
|
+
**Approach**: Separate callbacks (`on_startup`/`on_shutdown`)
|
|
12
|
+
|
|
13
|
+
**Why callbacks over block/yield pattern**:
|
|
14
|
+
- Simpler implementation (~10 lines vs ~20 lines with Fibers)
|
|
15
|
+
- Multiple hooks are natural (common use case: init DB, cache, metrics separately)
|
|
16
|
+
- Familiar to Ruby developers (Rails, Sinatra patterns)
|
|
17
|
+
- Block pattern can be added later if demand exists
|
|
18
|
+
|
|
19
|
+
**Why NOT shared state between lifecycle and routes**:
|
|
20
|
+
- Async gems (Sequel, HTTP clients) manage their own connection pools
|
|
21
|
+
- Long-lived resources are typically singletons/constants
|
|
22
|
+
- Routes already have dependency injection for per-request resources
|
|
23
|
+
- Keeps implementation simple
|
|
24
|
+
|
|
25
|
+
## API Design
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
app = FunApi::App.new do |api|
|
|
29
|
+
api.on_startup do
|
|
30
|
+
puts "Connecting to database..."
|
|
31
|
+
DB.connect
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
api.on_startup do
|
|
35
|
+
puts "Warming cache..."
|
|
36
|
+
Cache.warm
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
api.on_shutdown do
|
|
40
|
+
puts "Closing database..."
|
|
41
|
+
DB.disconnect
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
api.get '/users' do |input, req, task|
|
|
45
|
+
[DB[:users].all, 200]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Key behaviors**:
|
|
51
|
+
- Multiple hooks allowed (executed in registration order)
|
|
52
|
+
- Startup hooks run inside Async context, before server accepts requests
|
|
53
|
+
- Shutdown hooks run inside Async context, after server stops accepting requests
|
|
54
|
+
- Hooks can be async (have access to current Async::Task if needed)
|
|
55
|
+
- Errors in startup hooks should prevent server from starting
|
|
56
|
+
- Errors in shutdown hooks should be logged but not prevent other hooks from running
|
|
57
|
+
|
|
58
|
+
## Implementation
|
|
59
|
+
|
|
60
|
+
### 1. Application Changes (`lib/fun_api/application.rb`)
|
|
61
|
+
|
|
62
|
+
Add to `App` class:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
def initialize(...)
|
|
66
|
+
# ... existing code ...
|
|
67
|
+
@startup_hooks = []
|
|
68
|
+
@shutdown_hooks = []
|
|
69
|
+
|
|
70
|
+
yield self if block_given?
|
|
71
|
+
# ... rest of existing code ...
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def on_startup(&block)
|
|
75
|
+
raise ArgumentError, "on_startup requires a block" unless block_given?
|
|
76
|
+
@startup_hooks << block
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def on_shutdown(&block)
|
|
81
|
+
raise ArgumentError, "on_shutdown requires a block" unless block_given?
|
|
82
|
+
@shutdown_hooks << block
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def run_startup_hooks
|
|
87
|
+
@startup_hooks.each(&:call)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def run_shutdown_hooks
|
|
91
|
+
@shutdown_hooks.each do |hook|
|
|
92
|
+
hook.call
|
|
93
|
+
rescue => e
|
|
94
|
+
warn "Shutdown hook failed: #{e.message}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Falcon Server Changes (`lib/fun_api/server/falcon.rb`)
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
def self.start(app, host: "0.0.0.0", port: 3000)
|
|
103
|
+
Async do |task|
|
|
104
|
+
falcon_app = Protocol::Rack::Adapter.new(app)
|
|
105
|
+
endpoint = ::Async::HTTP::Endpoint.parse("http://#{host}:#{port}")
|
|
106
|
+
server = ::Falcon::Server.new(falcon_app, endpoint)
|
|
107
|
+
|
|
108
|
+
# Run startup hooks
|
|
109
|
+
app.run_startup_hooks if app.respond_to?(:run_startup_hooks)
|
|
110
|
+
|
|
111
|
+
puts "Falcon listening on #{host}:#{port}"
|
|
112
|
+
|
|
113
|
+
shutdown = -> {
|
|
114
|
+
puts "\nShutting down..."
|
|
115
|
+
app.run_shutdown_hooks if app.respond_to?(:run_shutdown_hooks)
|
|
116
|
+
task.stop
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
trap(:INT) { shutdown.call }
|
|
120
|
+
trap(:TERM) { shutdown.call }
|
|
121
|
+
|
|
122
|
+
server.run
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3. Accessor for Hooks (for testing)
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
# In Application class
|
|
131
|
+
attr_reader :startup_hooks, :shutdown_hooks
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Test Cases
|
|
135
|
+
|
|
136
|
+
Create `test/test_lifecycle.rb`:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
class TestLifecycle < Minitest::Test
|
|
140
|
+
def test_on_startup_registers_hook
|
|
141
|
+
app = FunApi::App.new do |api|
|
|
142
|
+
api.on_startup { :startup }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
assert_equal 1, app.startup_hooks.size
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_on_shutdown_registers_hook
|
|
149
|
+
app = FunApi::App.new do |api|
|
|
150
|
+
api.on_shutdown { :shutdown }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
assert_equal 1, app.shutdown_hooks.size
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_multiple_startup_hooks
|
|
157
|
+
app = FunApi::App.new do |api|
|
|
158
|
+
api.on_startup { :first }
|
|
159
|
+
api.on_startup { :second }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
assert_equal 2, app.startup_hooks.size
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_multiple_shutdown_hooks
|
|
166
|
+
app = FunApi::App.new do |api|
|
|
167
|
+
api.on_shutdown { :first }
|
|
168
|
+
api.on_shutdown { :second }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
assert_equal 2, app.shutdown_hooks.size
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def test_run_startup_hooks_executes_in_order
|
|
175
|
+
order = []
|
|
176
|
+
app = FunApi::App.new do |api|
|
|
177
|
+
api.on_startup { order << 1 }
|
|
178
|
+
api.on_startup { order << 2 }
|
|
179
|
+
api.on_startup { order << 3 }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
app.run_startup_hooks
|
|
183
|
+
|
|
184
|
+
assert_equal [1, 2, 3], order
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_run_shutdown_hooks_executes_in_order
|
|
188
|
+
order = []
|
|
189
|
+
app = FunApi::App.new do |api|
|
|
190
|
+
api.on_shutdown { order << 1 }
|
|
191
|
+
api.on_shutdown { order << 2 }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
app.run_shutdown_hooks
|
|
195
|
+
|
|
196
|
+
assert_equal [1, 2], order
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_shutdown_hook_error_does_not_stop_other_hooks
|
|
200
|
+
order = []
|
|
201
|
+
app = FunApi::App.new do |api|
|
|
202
|
+
api.on_shutdown { order << 1 }
|
|
203
|
+
api.on_shutdown { raise "error" }
|
|
204
|
+
api.on_shutdown { order << 3 }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
app.run_shutdown_hooks
|
|
208
|
+
|
|
209
|
+
assert_equal [1, 3], order
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def test_startup_hook_error_propagates
|
|
213
|
+
app = FunApi::App.new do |api|
|
|
214
|
+
api.on_startup { raise "startup failed" }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
assert_raises(RuntimeError) { app.run_startup_hooks }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_on_startup_requires_block
|
|
221
|
+
app = FunApi::App.new
|
|
222
|
+
|
|
223
|
+
assert_raises(ArgumentError) { app.on_startup }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_on_shutdown_requires_block
|
|
227
|
+
app = FunApi::App.new
|
|
228
|
+
|
|
229
|
+
assert_raises(ArgumentError) { app.on_shutdown }
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_on_startup_returns_self_for_chaining
|
|
233
|
+
app = FunApi::App.new
|
|
234
|
+
|
|
235
|
+
result = app.on_startup { :hook }
|
|
236
|
+
|
|
237
|
+
assert_same app, result
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def test_hooks_work_with_async_context
|
|
241
|
+
order = []
|
|
242
|
+
app = FunApi::App.new do |api|
|
|
243
|
+
api.on_startup do
|
|
244
|
+
Async do |task|
|
|
245
|
+
task.sleep(0.001)
|
|
246
|
+
order << :async_startup
|
|
247
|
+
end.wait
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
Async { app.run_startup_hooks }.wait
|
|
252
|
+
|
|
253
|
+
assert_equal [:async_startup], order
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Example Usage
|
|
259
|
+
|
|
260
|
+
Create `examples/lifecycle_demo.rb`:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
require_relative "../lib/fun_api"
|
|
264
|
+
require_relative "../lib/fun_api/server/falcon"
|
|
265
|
+
|
|
266
|
+
DB = { connected: false, users: [] }
|
|
267
|
+
CACHE = { warmed: false }
|
|
268
|
+
|
|
269
|
+
app = FunApi::App.new(
|
|
270
|
+
title: "Lifecycle Demo",
|
|
271
|
+
version: "1.0.0"
|
|
272
|
+
) do |api|
|
|
273
|
+
api.on_startup do
|
|
274
|
+
puts "Connecting to database..."
|
|
275
|
+
sleep 0.1 # Simulate connection time
|
|
276
|
+
DB[:connected] = true
|
|
277
|
+
DB[:users] = [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]
|
|
278
|
+
puts "Database connected!"
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
api.on_startup do
|
|
282
|
+
puts "Warming cache..."
|
|
283
|
+
sleep 0.05
|
|
284
|
+
CACHE[:warmed] = true
|
|
285
|
+
puts "Cache warmed!"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
api.on_shutdown do
|
|
289
|
+
puts "Closing database connection..."
|
|
290
|
+
DB[:connected] = false
|
|
291
|
+
puts "Database disconnected!"
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
api.on_shutdown do
|
|
295
|
+
puts "Clearing cache..."
|
|
296
|
+
CACHE[:warmed] = false
|
|
297
|
+
puts "Cache cleared!"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
api.get "/status" do |_input, _req, _task|
|
|
301
|
+
[{
|
|
302
|
+
db_connected: DB[:connected],
|
|
303
|
+
cache_warmed: CACHE[:warmed]
|
|
304
|
+
}, 200]
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
api.get "/users" do |_input, _req, _task|
|
|
308
|
+
[DB[:users], 200]
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
puts "Starting Lifecycle Demo..."
|
|
313
|
+
puts "Try: curl http://localhost:3000/status"
|
|
314
|
+
puts "Try: curl http://localhost:3000/users"
|
|
315
|
+
puts ""
|
|
316
|
+
|
|
317
|
+
FunApi::Server::Falcon.start(app, port: 3000)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Files to Modify
|
|
321
|
+
|
|
322
|
+
1. `lib/fun_api/application.rb` - Add hook registration and execution methods
|
|
323
|
+
2. `lib/fun_api/server/falcon.rb` - Call hooks at appropriate times
|
|
324
|
+
|
|
325
|
+
## Files to Create
|
|
326
|
+
|
|
327
|
+
1. `test/test_lifecycle.rb` - Lifecycle hook tests
|
|
328
|
+
2. `examples/lifecycle_demo.rb` - Demo application
|
|
329
|
+
|
|
330
|
+
## Files to Update
|
|
331
|
+
|
|
332
|
+
1. `AGENTS.md` - Add lifecycle hooks documentation
|
|
333
|
+
2. `README.md` - Add lifecycle hooks section
|
|
334
|
+
3. `.claude/PROJECT_PLAN.md` - Mark lifecycle hooks as complete
|
|
335
|
+
|
|
336
|
+
## Documentation Updates
|
|
337
|
+
|
|
338
|
+
### AGENTS.md Addition
|
|
339
|
+
|
|
340
|
+
```markdown
|
|
341
|
+
### Lifecycle Hooks
|
|
342
|
+
|
|
343
|
+
Run code at startup/shutdown:
|
|
344
|
+
```ruby
|
|
345
|
+
api.on_startup do
|
|
346
|
+
DB.connect
|
|
347
|
+
Cache.warm
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
api.on_shutdown do
|
|
351
|
+
DB.disconnect
|
|
352
|
+
end
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- Multiple hooks allowed (run in registration order)
|
|
356
|
+
- Startup hooks run before server accepts requests
|
|
357
|
+
- Shutdown hooks run after server stops
|
|
358
|
+
- Shutdown errors logged but don't stop other hooks
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### README.md Addition
|
|
362
|
+
|
|
363
|
+
```markdown
|
|
364
|
+
### 10. Lifecycle Hooks
|
|
365
|
+
|
|
366
|
+
Execute code when the application starts up or shuts down:
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
app = FunApi::App.new do |api|
|
|
370
|
+
api.on_startup do
|
|
371
|
+
puts "Connecting to database..."
|
|
372
|
+
DB.connect
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
api.on_startup do
|
|
376
|
+
puts "Warming cache..."
|
|
377
|
+
Cache.warm
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
api.on_shutdown do
|
|
381
|
+
puts "Disconnecting..."
|
|
382
|
+
DB.disconnect
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Key behaviors:**
|
|
388
|
+
- Multiple hooks supported (executed in registration order)
|
|
389
|
+
- Startup hooks run before server accepts requests
|
|
390
|
+
- Shutdown hooks run after server stops accepting requests
|
|
391
|
+
- Startup errors prevent server from starting
|
|
392
|
+
- Shutdown errors are logged but don't prevent other hooks from running
|
|
393
|
+
|
|
394
|
+
**Use cases:**
|
|
395
|
+
- Database connection pool initialization
|
|
396
|
+
- Cache warming
|
|
397
|
+
- Background task supervisor setup
|
|
398
|
+
- Metrics/logging initialization
|
|
399
|
+
- Graceful resource cleanup
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Success Criteria
|
|
403
|
+
|
|
404
|
+
1. `on_startup` registers hooks that run before server accepts requests
|
|
405
|
+
2. `on_shutdown` registers hooks that run after server stops
|
|
406
|
+
3. Multiple hooks execute in registration order
|
|
407
|
+
4. Shutdown hook errors don't prevent other hooks from running
|
|
408
|
+
5. All tests pass
|
|
409
|
+
6. Linter passes
|
|
410
|
+
7. Demo example works correctly
|
|
411
|
+
|
|
412
|
+
## Estimated Effort
|
|
413
|
+
|
|
414
|
+
~1-2 hours
|
|
415
|
+
|
|
416
|
+
## Notes
|
|
417
|
+
|
|
418
|
+
- Keep implementation minimal - this is a simple feature
|
|
419
|
+
- Don't over-engineer (no priority system, no async hook detection, etc.)
|
|
420
|
+
- The Falcon server integration is the key piece - hooks must run inside Async context
|
|
421
|
+
- Consider: should hooks have access to the app instance? (Probably not needed for v1)
|