4.6 KiB
request-pipeline-outcomes — driver config + success/failure result builders
Purpose
This module ties the bare emitters and validators together for the orchestrator. It exposes DriverConfig (the frozen per-pipeline settings bundle), OutcomeArgs (the common argument shape carried through every gate-fail branch), runRequestValidation (a thin adapter that parses the URL query, short-circuits on streaming / opt-out routes, and emits the dedicated api.validation.failures_total{where=request} counter), and the successResult / failResult builders that translate an outcome into a fully-shaped PipelineResult.
These helpers were extracted from request-pipeline.ts during the file-size refactor. Behaviour is byte-for-byte identical to the pre-split implementation; the only change is that the shared argument bundle is now reified as a type so each branch can use ...spread instead of re-listing eight parameters.
Features
DriverConfigfreezes the constructor inputs once and is threaded through every helper, so step-level functions see the sameboundary,now,instruments,defaultTenant, andresponseValidationmode.runRequestValidationis the only piece that knows about the request URL's query — the orchestrator does not parse it directly anymore.successResultalways incrementsapi.requests_total{outcome=success}and the duration histogram before returning.failResultincrements the same counter/histogram withoutcome=failure; the audit envelope is only emitted when a route matched (the 404 path skips audit per spec).OutcomeArgsis exported so the orchestrator can declare a singlecarryvariable and reuse it across every fail branch.
Usage: Basic
import {
type DriverConfig,
failResult,
runRequestValidation,
successResult,
} from "@suaveplan/api-host/request-pipeline-outcomes";
const validation = await runRequestValidation(config, { request, route, rootSpan });
if (validation.kind === "fail") {
return failResult(config, { ...carry, failure: validation.failure });
}
return successResult(config, { ...carry, response: handlerResponse });
Usage: Advanced
DriverConfig decouples the orchestrator from the RequestPipeline class — tests can construct a config object directly and call runPipeline to verify behaviour without instantiating the full class:
const config: DriverConfig = {
options,
instruments,
boundary: new ErrorBoundary({ instruments, logger }),
now: () => 1000,
idempotencyTtlMs: 60_000,
responseValidation: "enforce",
defaultTenant: null,
};
OutcomeArgs.route is nullable so the 404 path can reuse the same failResult shape. Tests that assert "no audit envelope on 404" rely on this branch — supplying a non-null route would silently emit an audit entry.
API Reference
interface DriverConfig {
options: RequestPipelineOptions;
instruments: HostInstruments;
boundary: ErrorBoundary;
now: () => number;
idempotencyTtlMs: number;
responseValidation: "enforce" | "warn";
defaultTenant: TenantContext | null;
}
interface OutcomeArgs {
request: Request;
requestId: string;
rootSpan: OtelSpanLike & BoundarySpan;
startedAt: number;
route: PipelineRoute | null;
principal: Principal | null;
tenantCtx: TenantContext | null;
}
function runRequestValidation(
config: DriverConfig,
args: { request: Request; route: PipelineRoute; rootSpan: OtelSpanLike & BoundarySpan }
): Promise<RequestValidationOk | RequestValidationFail>;
function successResult(config: DriverConfig, args: OutcomeArgs & { response: Response }): Promise<PipelineResult>;
function failResult(config: DriverConfig, args: OutcomeArgs & { failure: BoundaryFailure }): Promise<PipelineResult>;
Implementation Notes
runRequestValidation is the ONLY function that derives parsedQuery from the request URL. Centralising it ensures all three early-validation outcomes (streaming-skip, opt-out, schema-pass) return the same query record shape — without that, the handler ctx could see {} on one path and the parsed record on another.
successResult calls withRequestHeaders exactly once, deliberately. The original implementation also called it once and the post-extracted form preserves that — re-cloning the response would burn an unnecessary Headers allocation per request.
failResult always emits the api.requests_total{outcome=failure} counter, including on the 404 path. That contract is depended on by the api.errors_by_step dashboard the host's STATUS docs reference; removing it (e.g. as an optimisation for the no-route case) would silently break the metric.