first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
# `middleware`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `middleware` module defines the onion-model `Middleware` contract and
the `composeMiddleware` builder. Every adapter consumes the same shape so
middleware authored against the abstract contract drops into every router
implementation (Hono, in-memory, the worker-bridge router) unchanged.
The contract is intentionally narrow: a middleware receives the request
and a `next()` continuation, and must return a response. There is no
`ctx` mutation handle, no implicit state — middleware threads explicit
values via wrapping responses and inspecting the request shape.
## Features
- `Middleware<ReqBody, ResBody>` — type generic over the request body and
response body shapes so handler-level types flow through.
- `composeMiddleware([…])` — builds a single composed middleware from a
list; order is preserved.
- `next()` enforcement: a middleware that calls `next()` twice in a row
has its dispatch rejected. (Multiple sequential `next()` calls across
*different* middlewares are correct; only same-middleware repeats fail.)
- Synchronous return values are accepted (wrapped in `Promise.resolve`).
## Usage: Basic
```ts
import { composeMiddleware } from "@suaveplan/api-router/middleware";
const log = composeMiddleware([
async (req, next) => {
const res = await next();
return res; // pass-through with hook points
},
]);
```
## Usage: Advanced
```ts
import { composeMiddleware, type Middleware } from "@suaveplan/api-router/middleware";
const auth: Middleware = async (req, next) => {
if (req.headers.get("authorization") === null) {
return { status: 401, headers: {}, body: { code: "UNAUTH" }, streaming: false };
}
return next();
};
const trace: Middleware = async (req, next) => {
req.span.setAttribute("path", req.url);
const res = await next();
req.span.setAttribute("status", res.status);
return res;
};
const stack = composeMiddleware([trace, auth]);
// Execution: trace → auth → terminal → auth → trace
```
## API Reference
- `Middleware<ReqBody, ResBody>` — `(req, next) => RouterResponse | Promise<RouterResponse>`.
- `composeMiddleware(list)` — returns a single composed `Middleware`.
- `PipelineStep` — literal union of every per-request pipeline step name.
- `PLUGIN_INSERTION_STEP` — the constant `"post-validation"`; the single
insertion point plugin middleware is allowed.
- `assertPluginInsertionStep(step)` — throws `ROUTER.MIDDLEWARE_INSERTION_DENIED`
if `step` isn't the allowed boundary.
## Implementation Notes
`composeMiddleware` uses the textbook indexed-dispatch pattern: each call
to `next()` advances an internal index, and double-`next()` from the same
middleware is detected by comparing the new index to the recorded
high-watermark. The detection rejects the inner Promise rather than
throwing synchronously so the error propagates through the caller's
existing `.catch` / `try/await` machinery.
Synchronous middlewares are supported — the return value is wrapped in
`Promise.resolve` so callers don't have to choose between `async` and
plain functions. The terminal continuation is invoked with no arguments
and must produce the response; adapters wire this to the handler call.