Architecture
04. Request services and their lifetimes
How request processing acquires, shares, and releases application services.
Request services must outlive response construction when the body is still streaming. Effront acquires the application Layer per request and leaves response-body lifetime management to the host's HTTP boundary. Capturing host-owned services is a separate operation that transfers neither ownership nor lifetime.
The application definition supplies the Layer and compiled routes. packages/core/src/http.ts and server/application.ts connect them to each request.
1. Acquire services for the current request
toHttpEffect(application) handles the current HttpServerRequest and produces an HttpServerResponse. The caller supplies a request Scope and the application Layer's external requirements. Services are acquired when this Effect runs, not when it is created:
packages/core/src/http.tsexport const toHttpEffect = <Services, ApplicationError, Requirements>(
application: ApplicationDefinition<Services, ApplicationError, Requirements>,
): HttpApplicationEffect<ApplicationError, Requirements> =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest;
const length = request.headers["content-length"];
if (length !== undefined) {
const size = Number(length);
if (!Number.isSafeInteger(size) || size < 0 || size > maxRequestBodySize) {
return HttpServerResponse.text("Request body exceeds the 10 MiB limit.", { status: 413 });
}
}
// Request layers must not reuse instances from a host's construction memo map.
const memoMap = yield* Layer.makeMemoMap;
const handler = yield* HttpRouter.toHttpEffect(ServerApplication.httpLayer(application)).pipe(
Effect.provideService(Layer.CurrentMemoMap, memoMap),
);
const response = yield* handler;A fresh Layer.CurrentMemoMap prevents reuse of application instances memoized during host construction. HttpRouter.toHttpEffect builds the application HTTP Layer and immediately runs its handler in the current Context. Reusing the Effect still acquires the Layer per evaluation.
Before acquisition, a supplied Content-Length must convert to a safe nonnegative integer no greater than 10 MiB, or the entry returns 413. This checks the header, not the measured body size when the header is absent. Server Function decoding separately limits bytes read.
createFetchHandler in workers.ts uses HttpEffect.toWebHandler with this Effect. Each call adds a fresh WorkersRequestContext without changing request acquisition.
2. Make those services available to routes
ServerApplication.httpLayer combines the application Layer, FlightRenderer.layer, and HtmlRenderer.layer into RequestLayer. Layer.build(RequestLayer) produces the applicationServices Context. Before route execution, RequestContextMiddleware merges it into the live HTTP Context.
Services describes the application Layer's outputs. Requirements describes external services needed to construct or run it and remains in the HTTP Effect's type. Assembling the router does not satisfy those external requirements.
GET routes compose Page middleware through native Effect HTTP descriptors. POST first decodes the React function reference, then applies its middleware and any additional middleware needed to refresh the destination. Both use the request's acquired services, but function-specific middleware cannot be selected before decoding identifies the function.
3. Keep resources alive through the response body
A streaming HttpServerResponse still needs its request Scope after headers are produced. Effect HTTP's Web handler transfers that Scope to the body until completion, failure, or cancellation. Non-stream responses, such as generated text, release resources when handling finishes without waiting for a reader.
packages/core/src/http.ts/**
* Handles the current HTTP request in its host-owned scope.
*
* Application layers are built once per evaluation, using the current services.
* The host must retain the request scope until the body ends, fails, or is cancelled.
* Effect HTTP's Web handler performs this transfer automatically. Do not wrap
* response production alone in Effect.scoped: producing headers does not consume a body.
*/HEAD bodies are never consumed. Effect HTTP's Web handler and the native Node and Bun hosts discard the stream while preserving the headers and release the request Scope, so core passes the response through unchanged.
A direct HTTP host must preserve the same boundary. Applying Effect.scoped only to response production closes resources too early for deferred body work. A custom body that reads services later must also capture the needed Context: keeping services alive does not automatically provide them to a later Effect.
Flight rendering adds a child Scope and release operation tied to stream completion. Rendering explains how that child joins the response lifecycle.
4. Reuse host services without reusing request state
makeHttpEffect(application) captures references to external services and returns a reusable HTTP Effect. It neither builds the application's request Layer during construction nor takes ownership of the references. Their owner must keep them alive until all response bodies using them finish.
packages/core/src/http.ts const handler = toHttpEffect(application);
return Effect.map(Effect.context<CapturedRequirements<Requirements>>(), (context) => {
const captured: Context.Context<CapturedRequirements<Requirements>> =
captureExternalContext<HttpRequirements<Requirements>>(context);
return Effect.contextWith(
(requestContext: Context.Context<RemainingRequirements<Requirements>>) => {
const provided: Context.Context<CapturedRequirements<Requirements>> = Context.merge(
captured,
requestContext,
);
return Effect.provideContext(handler, provided);
},
);
});Live request values take precedence over captured values. Before merging them, captureExternalContext removes the construction-time Scope, HTTP request, parsed search parameters, route Context, router, and Layer memo map. This removal is explicit because Effect.context<R>() does not filter runtime keys by its type argument.
WorkersRequestContext holds readonly request, env, and executionContext fields. createWorkersContextAccessors creates typed readers for that reference, not a new Service or Layer. The types do not validate host values, and reading an absent reference throws TypeError.
@effront/cloudflare/workers specializes those readers with a CloudflareExecutionContext that includes waitUntil(Promise<unknown>). These host values stay in Effect Context rather than being implicitly serialized into Flight or HTML. The architecture overview places this adapter boundary alongside the rendering and browser entrypoints.