Summary
Introduce an immutable prepared-application representation that consumes WireframeApp's mutable registration state and builds route middleware chains exactly once.
This is the foundational implementation slice for ADR 012 proposal #637. It should establish the internal boundary without yet changing the server's public factory evaluation semantics; the following server-runtime issue will switch connection tasks onto the prepared root.
Problem
WireframeApp currently stores both:
- builder inputs:
handlers, middleware, callback registrations, protocol/assembler definitions, and configuration;
- runtime cache:
OnceCell<Arc<HashMap<u32, HandlerService<E>>>>.
handle_connection_result lazily initializes the route table and clones its inner Arc. When the server constructs a fresh app per connection, the cache has no opportunity to amortize its work across connections.
The route map and completed middleware chains are immutable runtime data. They should be produced by an explicit preparation transition rather than hidden inside connection handling.
Proposed design
Add an internal or initially crate-private transition shaped approximately as:
impl<S, C, E, F> WireframeApp<S, C, E, F> {
pub(crate) async fn prepare(self) -> Result<PreparedApp<S, C, E, F>, PrepareError>;
}
PreparedApp should own:
HashMap<u32, HandlerService<E>> directly;
- serializer and codec template/configuration;
- application data;
- lifecycle callback definitions;
- protocol and message assembler definitions;
- push, fragmentation, timeout, and memory-budget configuration.
Preparation should consume handlers and middleware, constructing each middleware chain once. Avoid retaining both the original handler map and a cloned route-service map after preparation unless a documented runtime need requires both.
Scope
Required
- Define
PreparedApp and any focused PrepareError type.
- Move
build_chains out of the per-connection path.
- Consume or otherwise finalize route and middleware registrations during preparation.
- Store the prepared route table by value, not as
OnceCell<Arc<HashMap<...>>>.
- Provide an internal compatibility path so existing tests can prepare an app before driving a connection.
- Keep the builder's type-changing methods working before preparation.
Deferred
- Evaluating
AppFactory once during server startup.
- Public constructor/deprecation decisions for
WireframeServer.
- Full
ConnectionRuntime extraction.
- Protocol ownership consolidation.
- Replacing every callback/assembler
Arc; that should happen after the prepared root is in use.
Acceptance criteria
Tests
- Prepare an app with multiple routes and middleware layers; assert transform counters equal one per layer/route as intended.
- Drive multiple in-process connections from one prepared app; assert no additional transform calls.
- Assert route dispatch and middleware ordering match current tests.
- Assert a failed transform/preparation returns an error before any runtime is available, if middleware transformation can fail after the supporting error work lands.
- Add a compile-time or type-level test that route registration is unavailable after transitioning to
PreparedApp.
Design constraints
- Do not make
PreparedApp mutable behind a lock.
- Do not clone the entire builder to preserve registrations after preparation.
- Do not introduce a second public route-registration surface on
PreparedApp.
- Keep ADR 010's packet/transport-frame boundary unchanged.
Dependencies
This issue should land before the server-runtime and connection-runtime slices.
References
Summary
Introduce an immutable prepared-application representation that consumes
WireframeApp's mutable registration state and builds route middleware chains exactly once.This is the foundational implementation slice for ADR 012 proposal #637. It should establish the internal boundary without yet changing the server's public factory evaluation semantics; the following server-runtime issue will switch connection tasks onto the prepared root.
Problem
WireframeAppcurrently stores both:handlers,middleware, callback registrations, protocol/assembler definitions, and configuration;OnceCell<Arc<HashMap<u32, HandlerService<E>>>>.handle_connection_resultlazily initializes the route table and clones its innerArc. When the server constructs a fresh app per connection, the cache has no opportunity to amortize its work across connections.The route map and completed middleware chains are immutable runtime data. They should be produced by an explicit preparation transition rather than hidden inside connection handling.
Proposed design
Add an internal or initially crate-private transition shaped approximately as:
PreparedAppshould own:HashMap<u32, HandlerService<E>>directly;Preparation should consume
handlersandmiddleware, constructing each middleware chain once. Avoid retaining both the original handler map and a cloned route-service map after preparation unless a documented runtime need requires both.Scope
Required
PreparedAppand any focusedPrepareErrortype.build_chainsout of the per-connection path.OnceCell<Arc<HashMap<...>>>.Deferred
AppFactoryonce during server startup.WireframeServer.ConnectionRuntimeextraction.Arc; that should happen after the prepared root is in use.Acceptance criteria
WireframeApphas an explicit consuming preparation transition.PreparedAppcontains a direct prepared route table.PreparedAppreuse the same route services without rebuilding chains.OnceCell<Arc<HashMap<...>>>remains in the prepared runtime representation.Tests
PreparedApp.Design constraints
PreparedAppmutable behind a lock.PreparedApp.Dependencies
This issue should land before the server-runtime and connection-runtime slices.
References
src/app/builder/core.rssrc/app/builder/routing.rssrc/app/inbound_handler.rssrc/middleware.rs