Initial Commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# `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
|
||||
|
||||
- `DriverConfig` freezes the constructor inputs once and is threaded through every helper, so step-level functions see the same `boundary`, `now`, `instruments`, `defaultTenant`, and `responseValidation` mode.
|
||||
- `runRequestValidation` is the only piece that knows about the request URL's query — the orchestrator does not parse it directly anymore.
|
||||
- `successResult` always increments `api.requests_total{outcome=success}` and the duration histogram before returning.
|
||||
- `failResult` increments the same counter/histogram with `outcome=failure`; the audit envelope is only emitted when a route matched (the 404 path skips audit per spec).
|
||||
- `OutcomeArgs` is exported so the orchestrator can declare a single `carry` variable and reuse it across every fail branch.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user