@tetsujs/request-id gives every request an id, in the context and on the
response, so the log lines and failure reports of one request can be joined.
It is one beforeParse hook.
Every response gets an x-request-id header, and ctx.requestId holds the id
for the rest of the request. The records of
@tetsujs/request-log carry it when this hook
runs before them.
Mounted on the application, ctx.requestId is there at runtime but not in a
route’s types: the application cannot type the routes it holds (see
Context and its types). To have it typed in a
handler, mount the hook on the route:
constid=requestId();
constlist=route({
method: "GET"
The method this route answers.
Kept as a literal rather than widened to
Method
: the method is
half of a route's identity, and an application that remembers its
routes — for a generated client, for tooling — needs to know which one
this is.
method: "GET",
path: "/orders"
Route path with :param segments, e.g. "/orders/:id/cancel".
Must start with /, contain no empty segments and no trailing slash;
a malformed literal is a compile error.
path: "/orders",
hooks: { beforeParse: [id] },
handler: (ctx: {
readonlyreq:Request& {
readonlycookies?:Bun.CookieMap;
};
readonlyserver:Bun.Server<unknown>;
readonlyout:Outgoing;
readonlyroute:RouteInfo;
readonlystartedAt:number;
readonlyparams: {};
requestId:string;
}) =>void
The endpoint logic; ctx is fully inferred, never annotate it.
The return type is inferred rather than demanded, and checked twice
over. Against the route's own contract, by the
HandlerResult
bound on R: answering with something response never declared is a
compile error that says so, instead of a structural diff against
Response. And against what the framework can serialize at all, by
the intersected
ValidateResult
: a stream handed over bare is
refused whether or not the route declared anything, because that is
the case no contract covers — without a response schema
HandlerResult is unknown and accepts every value there is.
handler: (
ctx: {
readonly req: Request & {
readonly cookies?: Bun.CookieMap;
};
readonly server: Bun.Server<unknown>;
readonly out: Outgoing;
readonly route: RouteInfo;
readonly startedAt: number;
readonly params: {};
requestId: string;
}
ctx) =>
constlogger: {
info(fields:object, message:string):void;
}
logger.info({ requestId:
ctx: {
readonly req: Request & {
readonly cookies?: Bun.CookieMap;
};
readonly server: Bun.Server<unknown>;
readonly out: Outgoing;
readonly route: RouteInfo;
readonly startedAt: number;
readonly params: {};
requestId: string;
}
ctx.requestId }, "listing"),
requestId: string
});
Or declare it where it is read, with Requires<{ requestId: string }>, and the
compiler checks that something provides it. An application hook mounted after
requestId() sees it typed too.
the header the id is written to, and read from when trusted
trustIncoming
false
use the id the client sent; an empty header counts as none
generate
crypto.randomUUID
how a new id is made
constid=requestId({
header?: string |undefined
The header the id is written to, and read from when trusted.
Defaults to x-request-id.
header: "x-correlation-id",
trustIncoming?: boolean |undefined
Whether an incoming header is trusted.
Off by default, and deliberately: an id from the outside is a value a
client chose, so trusting it lets one client stamp another's log
lines. Turn it on only behind a proxy that overwrites the header.
An empty header counts as none, and the id is generated.
trustIncoming: true });
Turn on trustIncoming only behind a proxy that overwrites the header. An
incoming id is chosen by the client, and trusting it lets one client stamp
another’s log lines.
Failures share the id when you pass reportError to createApp and log
ctx?.requestId with the error. See Logging.
The package also exports the types RequestIdOptions and RequestIdHook.
Type a hook with ReturnType<typeof requestId> rather than AnyHook, which
erases both the slot and the requestId it adds to the context.
Code that never receives ctx, such as a repository several calls down,
can read the id through AsyncLocalStorage, filled by a hook mounted after
requestId(). Logging
shows the hook and a pino mixin that puts the id on every line.