Skip to content

refactor(event-handler-core): make the handler a DI-native app - #5532

Draft
adrians5j wants to merge 16 commits into
nextfrom
adrian/di-native-handler-runtime
Draft

refactor(event-handler-core): make the handler a DI-native app#5532
adrians5j wants to merge 16 commits into
nextfrom
adrian/di-native-handler-runtime

Conversation

@adrians5j

Copy link
Copy Markdown
Member

What

Turns the createHandler closure 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:

Abstraction Role
HandlerRuntime orchestrates root → child → event-type match → executeChain
RootContainerFactory builds/memoizes the root once per process (honors the prebuilt-rootContainer path)
ChildContainerFactory creates + sets up the per-request child: transport.bind, request setup, RequestInitializer loop

createHandler is now thin: build app container → register the three defaults (singleton-scoped) → run the app? decoration seam → resolve HandlerRuntime.

Why

createHandler was 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 on ChildContainerFactory, so register-time feature-flag checks see the live license. First slice of plans/licensing-feature-flags.md.

Behavior-preserving

  • External createHandler option surface unchanged — only an additive app? decoration hook.
  • RequestInitializer still runs at the same point (relocated into ChildContainerFactory, intact).
  • No consumer changes needed (createLambdaHandler, createServerHandler, createTestHttpHandler untouched).

Verification

  • event-handler-core: 26 tests pass (23 existing + 3 new in HandlerRuntime.test.ts).
  • New tests prove: dispatch works, a ChildContainerFactory decorator runs per request, and the root is built once / reused across warm invocations.
  • Builds green: event-handler-core, event-handler-aws (server + api-event-handler-core API change is purely additive).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga

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
@adrians5j adrians5j added the evh-cleanups event-handler DI/transport-agnostic cleanups label Aug 3, 2026
adrians5j and others added 15 commits August 3, 2026 15:29
…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
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
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

evh-cleanups event-handler DI/transport-agnostic cleanups

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant