first commit
This commit is contained in:
88
docs/api-stack/api/api-router/src/middleware/middleware.md
Normal file
88
docs/api-stack/api/api-router/src/middleware/middleware.md
Normal 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.
|
||||
Reference in New Issue
Block a user