refactor(event-handler-core): make the handler a DI-native app - #5532
Draft
adrians5j wants to merge 16 commits into
Draft
refactor(event-handler-core): make the handler a DI-native app#5532adrians5j wants to merge 16 commits into
adrians5j wants to merge 16 commits into
Conversation
Replace the `createHandler` closure with a small DI app built in an "app container" (distinct from the per-process root and per-request child containers it goes on to create). Three decoratable abstractions own the lifecycle: - HandlerRuntime — orchestrates root -> child -> event match -> dispatch - RootContainerFactory — builds/memoizes the root once per process - ChildContainerFactory — creates + sets up the per-request child (transport bind, request setup, RequestInitializer loop) Behavior-preserving: the external `createHandler` option surface is unchanged (only an additive `app?` decoration hook is added), and RequestInitializer still runs where it did. The new seams let the composition layer extend the lifecycle by decoration instead of growing branches in createHandler — e.g. a future pre-register license refresh decorator on ChildContainerFactory. First slice of plans/licensing-feature-flags.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…rConfig Drop the separate `CreateHandlerOptions` interface; `createHandler` now takes `HandlerConfig` directly and registers it as-is (no field remapping). The config the caller writes is exactly the DI value the lifecycle factories resolve — the connection is visible at a glance. Transport defaulting moves into ChildContainerFactory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…style) Mirror the ProjectSdk entrypoint pattern: the handler is now a class with a static `init(config)` factory that builds the app container and returns a runtime whose `handle(...)` is the platform-invocable. Folds the former HandlerRuntime DI abstraction into the class and removes the `createHandler` function. Root/ChildContainerFactory remain the decoratable DI seams. Updated callers (createLambdaHandler, createServerHandler, createTestHttpHandler) and tests to `HandlerRuntime.init(...).handle(...)`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
The app-level orchestrator's primary job is dispatch: match the incoming event to its EventType and route it to that type's handler chain. Renamed to EventDispatcher (aligns with the existing "dispatch" vocabulary; no clash with the EventHandler leaf). Entry point is `EventDispatcher.init(config).handle(...)`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…lt impls Mirror the api-core layout: DI contracts (HandlerConfig, RootContainerFactory, ChildContainerFactory — token + interface + namespace) move to an `abstractions.ts`; the default implementations stay in their own files (DefaultRootContainerFactory, DefaultChildContainerFactory). Consumers import the contract from ./abstractions and the default impl from its file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Final name for the app-level orchestrator: it takes an event and processes it end-to-end (setup containers, route, run the handler chain) and returns the result — captures the return that "dispatcher" undersells, and pairs with the EventHandler leaf (processor orchestrates, handler handles). Method renamed handle → process for cohesion: `EventProcessor.init(config).process(event)`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Final name: the app-level orchestrator is the handler app for this package — HandlerApp.init(config).handle(event). Within event-handler-core "the app" needs no Event prefix, and HandlerApp avoids the look-alike with the EventHandler leaf. Method reverted to handle (natural for an app). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Settle on EventDispatcher for the app-level orchestrator: EventDispatcher.init(config).handle(event). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Final name: HandlerApp.init(config).handle(event) — used like Express's `const app = ...`. Within event-handler-core "the app" needs no Event prefix and avoids the EventHandler look-alike. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…layering Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…option The Node server no longer builds the root itself and injects it — it calls `app.getRootContainer()` to get the eagerly-built root for onServer (WebSockets upgrade wiring). HandlerApp now fully owns root creation, so the `rootContainer` HandlerConfig escape hatch is removed (RootContainerFactory builds it lazily, memoized; getRootContainer just triggers that build early). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
… is set rootContainer is consumed solely inside the onServer block (WebSockets + scheduler startup wiring). Move the getRootContainer() call into that branch so a server with no onServer lets the root build lazily on first request. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…tainer rename Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…tureFlags query, demote wcp) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Turns the
createHandlerclosure into a DI-native handler app. The handler now lives in a small "app container" (distinct from the per-process root container and the per-request child container it creates), with three decoratable lifecycle abstractions:HandlerRuntimeexecuteChainRootContainerFactoryrootContainerpath)ChildContainerFactorytransport.bind, request setup,RequestInitializerloopcreateHandleris now thin: build app container → register the three defaults (singleton-scoped) → run theapp?decoration seam → resolveHandlerRuntime.Why
createHandlerwas a hardcoded closure — no way to extend a lifecycle step without adding branches. Making each step a decoratable abstraction gives the composition layer real seams. The immediate consumer (next PRs) is a pre-register license refresh decorator onChildContainerFactory, so register-time feature-flag checks see the live license. First slice ofplans/licensing-feature-flags.md.Behavior-preserving
createHandleroption surface unchanged — only an additiveapp?decoration hook.RequestInitializerstill runs at the same point (relocated intoChildContainerFactory, intact).createLambdaHandler,createServerHandler,createTestHttpHandleruntouched).Verification
HandlerRuntime.test.ts).ChildContainerFactorydecorator runs per request, and the root is built once / reused across warm invocations.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga