@znight/http 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2163 @@
1
+ import { t as RoutePath } from "./path-JI87C4k8.mjs";
2
+ import { ClassMarker, Container, MethodMarker, ParameterMarker, Plugin, PluginContext, PluginOptions, ProviderKey, Scope, Token } from "@znight/core";
3
+ /**
4
+ * Cookies (RFC 0006): parsing of the `cookie` request header, canonical value encoding, strict
5
+ * `Set-Cookie` serialisation (RFC 6265bis: attribute validation, name prefixes, size limits),
6
+ * `Set-Cookie` parsing (tests) and the response cookie jar.
7
+ *
8
+ * This module is import-free (pure functions): safe to import from anywhere, the compiler excepted
9
+ * (it has its own token grammar).
10
+ */
11
+ /** Options of a cookie set on a response (`ctx.setCookie()`, `HttpResponse.withCookie()`). */
12
+ interface CookieOptions {
13
+ /** Lifetime in seconds (integer). `0` or less expires the cookie now. Default: session cookie. */
14
+ readonly maxAge?: number;
15
+ /**
16
+ * Absolute expiry (sent as an IMF-fixdate), in the years 1601 to 9999 (browsers ignore other
17
+ * years). Browsers cap lifetimes at 400 days: prefer `maxAge` for long-lived cookies.
18
+ */
19
+ readonly expires?: Date;
20
+ /**
21
+ * Domain (sub-domains included). Default: none, a host-only cookie. `null`: host-only even when
22
+ * `cookies.defaults` sets a domain. `__Host-` cookies never take the default domain.
23
+ */
24
+ readonly domain?: string | null;
25
+ /** Default: `'/'`. */
26
+ readonly path?: string;
27
+ /** Default: `true` (browsers only send it over HTTPS, `http://localhost` excepted). */
28
+ readonly secure?: boolean;
29
+ /** Default: `true` (not readable from JavaScript). */
30
+ readonly httpOnly?: boolean;
31
+ /** Default: `'lax'`. `'none'` requires `secure`. */
32
+ readonly sameSite?: "strict" | "lax" | "none";
33
+ /** CHIPS partitioned cookie. Requires `secure`. Default: `false`. */
34
+ readonly partitioned?: boolean;
35
+ /** Chromium `Priority`. Default: none. */
36
+ readonly priority?: "low" | "medium" | "high";
37
+ /** Signs the value with the first secret of the plugin option `cookies.secrets`. Default: `false`. */
38
+ readonly signed?: boolean;
39
+ }
40
+ /** Plugin-wide defaults (`cookies.defaults`). Lifetime and signing are per cookie. */
41
+ type CookieDefaults = Omit<CookieOptions, "maxAge" | "expires" | "signed">;
42
+ /** What identifies a cookie to clear (and keeps prefixed names valid). */
43
+ type ClearCookieOptions = Pick<CookieOptions, "domain" | "path" | "secure" | "httpOnly" | "sameSite" | "partitioned">;
44
+ /** A cookie to send, as given (defaults and signing are applied when the response is serialised). */
45
+ interface ResponseCookie {
46
+ readonly name: string;
47
+ /** `undefined`: a clearing cookie. */
48
+ readonly value: string | undefined;
49
+ readonly options: CookieOptions;
50
+ }
51
+ /** A parsed `Set-Cookie` line (see {@link parseSetCookie}). */
52
+ interface SetCookieInfo {
53
+ readonly name: string;
54
+ /** Decoded value (a signed cookie: `"<value>.<signature>"`, decoded). */
55
+ readonly value: string;
56
+ /** The header line. */
57
+ readonly raw: string;
58
+ readonly expires?: Date;
59
+ readonly maxAge?: number;
60
+ readonly domain?: string;
61
+ readonly path?: string;
62
+ readonly secure: boolean;
63
+ readonly httpOnly: boolean;
64
+ readonly sameSite?: "strict" | "lax" | "none";
65
+ readonly partitioned: boolean;
66
+ readonly priority?: "low" | "medium" | "high";
67
+ }
68
+ /**
69
+ * Parses a `cookie` request header into a prototype-less, frozen record (`__proto__` is just a
70
+ * name). Pairs are split on `;` only (never `,`) and trimmed; a pair without `=` or with an empty
71
+ * name is ignored; names are kept as sent (case-sensitive); a value wrapped in double quotes is
72
+ * unquoted, and percent-decoded when it contains `%` (a malformed sequence keeps the raw value).
73
+ * When a name repeats, the first occurrence wins (browsers send the most specific path first).
74
+ * Linear time, never throws.
75
+ */
76
+ declare function parseCookieHeader(header: string | undefined): Readonly<Record<string, string>>;
77
+ /**
78
+ * The `Set-Cookie` line of a cookie with the secure defaults
79
+ * (`Path=/; Secure; HttpOnly; SameSite=Lax`, overridable per cookie), in the order
80
+ * `name=value; Max-Age; Expires; Domain; Path; Secure; HttpOnly; SameSite; Partitioned; Priority`.
81
+ * The value is percent-encoded (see {@link encodeCookieValue}). Signing (`signed: true`) needs the
82
+ * secrets of the plugin: use `ctx.setCookie()` or `HttpPlugin.signCookie()`.
83
+ *
84
+ * ```ts
85
+ * serializeCookie('theme', 'dark', { maxAge: 86_400, httpOnly: false })
86
+ * // theme=dark; Max-Age=86400; Path=/; Secure; SameSite=Lax
87
+ * ```
88
+ *
89
+ * @throws TypeError for an invalid name, value or attribute, a `SameSite=None` or partitioned cookie
90
+ * without `Secure`, a broken `__Secure-` / `__Host-` / `__Http-` / `__Host-Http-` prefix rule.
91
+ * @throws RangeError when `name=value` exceeds 4096 bytes, or `Domain` / `Path` 1024 bytes.
92
+ */
93
+ declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
94
+ /**
95
+ * The `Set-Cookie` line clearing a cookie: `name=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT`
96
+ * plus `Domain`, `Path`, `Secure`, `HttpOnly`, `SameSite` and `Partitioned` (secure defaults merged
97
+ * with `options`), so that a prefixed cookie is cleared with valid attributes.
98
+ */
99
+ declare function serializeClearCookie(name: string, options?: ClearCookieOptions): string;
100
+ /**
101
+ * Parses a `Set-Cookie` line (attribute names are case-insensitive; the value is decoded like a
102
+ * request cookie). Used by `inject()` to fill `response.cookies`.
103
+ *
104
+ * @throws TypeError when the line has no `name=value` pair.
105
+ */
106
+ declare function parseSetCookie(line: string): SetCookieInfo;
107
+ /**
108
+ * A copy of the [Standard Schema v1](https://standardschema.dev) interface, flattened (no namespace,
109
+ * so the file stays erasable). Every compliant library (Zod, Valibot, ArkType, Effect Schema...)
110
+ * produces values assignable to {@link StandardSchemaV1}: no adapter and no dependency are needed.
111
+ */
112
+ /** A schema implementing the Standard Schema v1 interface. */
113
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
114
+ /** The Standard Schema properties. */
115
+ readonly "~standard": StandardSchemaV1Props<Input, Output>;
116
+ }
117
+ /** The Standard Schema properties. */
118
+ interface StandardSchemaV1Props<Input = unknown, Output = Input> {
119
+ /** The version number of the standard. */
120
+ readonly version: 1;
121
+ /** The vendor name of the schema library. */
122
+ readonly vendor: string;
123
+ /** Validates an unknown input value (synchronously or not). */
124
+ readonly validate: (value: unknown, options?: StandardSchemaV1Options | undefined) => StandardSchemaV1Result<Output> | Promise<StandardSchemaV1Result<Output>>;
125
+ /** Inferred types associated with the schema. */
126
+ readonly types?: StandardSchemaV1Types<Input, Output> | undefined;
127
+ }
128
+ /** Options of {@link StandardSchemaV1Props.validate}. */
129
+ interface StandardSchemaV1Options {
130
+ /** Vendor-specific parameters. */
131
+ readonly libraryOptions?: Record<string, unknown> | undefined;
132
+ }
133
+ /** Result of {@link StandardSchemaV1Props.validate}. */
134
+ type StandardSchemaV1Result<Output> = StandardSchemaV1SuccessResult<Output> | StandardSchemaV1FailureResult;
135
+ /** Successful validation: the (possibly transformed) output value. */
136
+ interface StandardSchemaV1SuccessResult<Output> {
137
+ readonly value: Output;
138
+ readonly issues?: undefined;
139
+ }
140
+ /** Failed validation. */
141
+ interface StandardSchemaV1FailureResult {
142
+ readonly issues: readonly StandardSchemaV1Issue[];
143
+ }
144
+ /** One validation issue. */
145
+ interface StandardSchemaV1Issue {
146
+ readonly message: string;
147
+ readonly path?: readonly (PropertyKey | StandardSchemaV1PathSegment)[] | undefined;
148
+ }
149
+ /** A path segment of an issue. */
150
+ interface StandardSchemaV1PathSegment {
151
+ readonly key: PropertyKey;
152
+ }
153
+ /** Input and output types of a schema. */
154
+ interface StandardSchemaV1Types<Input = unknown, Output = Input> {
155
+ readonly input: Input;
156
+ readonly output: Output;
157
+ }
158
+ /** The input type of a schema. */
159
+ type InferSchemaInput<S extends StandardSchemaV1> = NonNullable<S["~standard"]["types"]>["input"];
160
+ /** The output type of a schema (what handlers receive). */
161
+ type InferSchemaOutput<S extends StandardSchemaV1> = NonNullable<S["~standard"]["types"]>["output"];
162
+ /** Whether `value` looks like a Standard Schema v1 (has a `~standard` object with a `validate` function). */
163
+ declare function isStandardSchema(value: unknown): value is StandardSchemaV1;
164
+ /** Response header values: a single value or several (`set-cookie`). */
165
+ type HeaderValue = string | readonly string[];
166
+ /** Options of {@link HttpException}. */
167
+ interface HttpExceptionOptions extends ErrorOptions {
168
+ /** Headers added to the error response (`www-authenticate`, `retry-after`, `allow`...). */
169
+ readonly headers?: Readonly<Record<string, HeaderValue>>;
170
+ }
171
+ /** JSON body of an error response. */
172
+ interface HttpErrorBody {
173
+ readonly statusCode: number;
174
+ /** Reason phrase of the status (`Not Found`). */
175
+ readonly error: string;
176
+ readonly message: string;
177
+ /** Structured details (validation issues...), omitted when absent. */
178
+ readonly details?: unknown;
179
+ }
180
+ /** One entry of the `details` of a validation error. */
181
+ interface ValidationIssueDetail {
182
+ /** Dotted path of the invalid value inside the validated input (`user.email`, `items.0`), `''` for the root. */
183
+ readonly path: string;
184
+ readonly message: string;
185
+ }
186
+ /**
187
+ * An error carrying an HTTP status. Thrown from handlers, guards, interceptors or services, it is
188
+ * rendered as `{ statusCode, error, message, details? }` with its status and headers.
189
+ *
190
+ * ```ts
191
+ * throw new HttpException(409, 'Email already used', { field: 'email' })
192
+ * throw new NotFoundException(`User ${id} not found`)
193
+ * throw new UnauthorizedException('Token expired', undefined, { headers: { 'www-authenticate': 'Bearer' } })
194
+ * ```
195
+ *
196
+ * Invalid statuses (not an integer in 100–599) become `500`.
197
+ */
198
+ declare class HttpException extends Error {
199
+ /** HTTP status of the response. */
200
+ readonly status: number;
201
+ /** Structured details rendered in the body (omitted when `undefined`). */
202
+ readonly details: unknown;
203
+ /** Headers added to the response. */
204
+ readonly headers: Readonly<Record<string, HeaderValue>>;
205
+ constructor(status: number, message?: string, details?: unknown, options?: HttpExceptionOptions);
206
+ /** The `statusCode` field of the body (alias of {@link status}). */
207
+ get statusCode(): number;
208
+ /** Body of the error response. Override it in a subclass to change the shape. */
209
+ toBody(): HttpErrorBody;
210
+ }
211
+ /** Whether `value` is an {@link HttpException} (also across duplicated package copies). */
212
+ declare function isHttpException(value: unknown): value is HttpException;
213
+ /** 400 Bad Request. */
214
+ declare class BadRequestException extends HttpException {
215
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
216
+ }
217
+ /**
218
+ * 400 Bad Request raised when an input fails its schema (`@Body(schema)`, `@Query(schema)`...).
219
+ * `details` lists the issues as `{ path, message }`; the raw Standard Schema issues are kept in
220
+ * {@link issues} for exception mappers.
221
+ */
222
+ declare class ValidationException extends BadRequestException {
223
+ /** What was validated: `body`, `query`, `query "page"`, `param "id"`... */
224
+ readonly location: string;
225
+ readonly issues: readonly StandardSchemaV1Issue[];
226
+ constructor(location: string, issues: readonly StandardSchemaV1Issue[], options?: HttpExceptionOptions);
227
+ }
228
+ /** Converts a Standard Schema issue to `{ path, message }` (`path` dotted, `''` for the root). */
229
+ declare function toIssueDetail(issue: StandardSchemaV1Issue): ValidationIssueDetail;
230
+ /** 401 Unauthorized. */
231
+ declare class UnauthorizedException extends HttpException {
232
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
233
+ }
234
+ /** 403 Forbidden (also the response of a guard returning `false`). */
235
+ declare class ForbiddenException extends HttpException {
236
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
237
+ }
238
+ /** 404 Not Found. */
239
+ declare class NotFoundException extends HttpException {
240
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
241
+ }
242
+ /** 405 Method Not Allowed (pass the `allow` header in `options.headers`). */
243
+ declare class MethodNotAllowedException extends HttpException {
244
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
245
+ }
246
+ /** 409 Conflict. */
247
+ declare class ConflictException extends HttpException {
248
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
249
+ }
250
+ /** 410 Gone. */
251
+ declare class GoneException extends HttpException {
252
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
253
+ }
254
+ /** 413 Payload Too Large (request body above `bodyLimit`). */
255
+ declare class PayloadTooLargeException extends HttpException {
256
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
257
+ }
258
+ /** 415 Unsupported Media Type. */
259
+ declare class UnsupportedMediaTypeException extends HttpException {
260
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
261
+ }
262
+ /** 422 Unprocessable Entity. */
263
+ declare class UnprocessableEntityException extends HttpException {
264
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
265
+ }
266
+ /** 429 Too Many Requests (pass `retry-after` in `options.headers`). */
267
+ declare class TooManyRequestsException extends HttpException {
268
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
269
+ }
270
+ /** 500 Internal Server Error (its message is always sent, unlike unexpected errors). */
271
+ declare class InternalServerErrorException extends HttpException {
272
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
273
+ }
274
+ /** 501 Not Implemented. */
275
+ declare class NotImplementedException extends HttpException {
276
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
277
+ }
278
+ /** 502 Bad Gateway. */
279
+ declare class BadGatewayException extends HttpException {
280
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
281
+ }
282
+ /** 503 Service Unavailable. */
283
+ declare class ServiceUnavailableException extends HttpException {
284
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
285
+ }
286
+ /** 504 Gateway Timeout. */
287
+ declare class GatewayTimeoutException extends HttpException {
288
+ constructor(message?: string, details?: unknown, options?: HttpExceptionOptions);
289
+ }
290
+ /** Parsed query string (or `application/x-www-form-urlencoded` body): repeated keys become arrays. */
291
+ type QueryRecord = Record<string, string | string[]>;
292
+ /**
293
+ * Parses a query string (with or without the leading `?`) with `URLSearchParams` semantics (`+` is
294
+ * a space, malformed `%` sequences are kept as is, invalid UTF-8 becomes U+FFFD). A key present
295
+ * once maps to a string, a repeated key to an array of strings, in order. No nesting (`a[b]=1` is
296
+ * the key `a[b]`). The returned object has no prototype.
297
+ *
298
+ * `parseQuery('?tag=a&tag=b&page=2')` → `{ tag: ['a', 'b'], page: '2' }`
299
+ */
300
+ declare function parseQuery(query: string): QueryRecord;
301
+ /**
302
+ * Byte sizes (`1048576`, `'512kb'`, `'10 MiB'`, `'1.5gb'`) used by body and upload limits. This
303
+ * module is **pure** (no imports, no I/O): the compiler extension evaluates `@BodyLimit('10mb')` and
304
+ * `maxSize` options with it at build time.
305
+ *
306
+ * Units are case-insensitive and binary, like the `bytes` package used by Express:
307
+ * `b`, `kb`/`kib`/`k` = 1024, `mb`/`mib`/`m` = 1024², `gb`/`gib`/`g` = 1024³, `tb`/`tib`/`t` = 1024⁴.
308
+ */
309
+ /**
310
+ * A size in bytes: a non-negative integer (`Infinity` disables the limit) or a string with a unit
311
+ * (`'512kb'`, `'10mb'`, `'1.5 GiB'`; `kb` = 1024 bytes).
312
+ */
313
+ type ByteSize = number | string;
314
+ /**
315
+ * Parses a {@link ByteSize}: `1024` → `1024`, `'10mb'` → `10485760`, `'1.5kb'` → `1536` (fractions
316
+ * are rounded down). Returns `undefined` for anything else (negative or fractional numbers,
317
+ * unknown units, `NaN`).
318
+ */
319
+ declare function parseByteSize(value: unknown): number | undefined;
320
+ /** Human-readable size for messages: `5 MiB`, `1.5 KiB`, `512 bytes`, `unlimited`. */
321
+ declare function formatByteSize(bytes: number): string;
322
+ /** Options of `@UploadedFile` (static: evaluated at build time). */
323
+ interface UploadedFileOptions {
324
+ /**
325
+ * Answer 400 when no file is sent. Default: `true`, unless the parameter is optional
326
+ * (`file?: File`, `File | undefined`).
327
+ */
328
+ readonly required?: boolean;
329
+ /** Maximum size of the file (`5_000_000`, `'5mb'`; `kb` = 1024 bytes). Default: plugin `uploads.maxFileSize`. */
330
+ readonly maxSize?: ByteSize;
331
+ /**
332
+ * Accepted media types, as declared by the client: `'image/png'`, `'image/*'`, `'*\/*'`. Other
333
+ * types answer 415. Default: any.
334
+ */
335
+ readonly mimeTypes?: readonly string[];
336
+ }
337
+ /** Options of `@UploadedFiles` (static: evaluated at build time). */
338
+ interface UploadedFilesOptions extends UploadedFileOptions {
339
+ /** Maximum number of files (413 above). Default: plugin `uploads.maxFiles`. */
340
+ readonly maxFiles?: number;
341
+ }
342
+ /** Plugin-wide upload limits (`HttpPlugin.configure({ uploads })`). */
343
+ interface UploadLimits {
344
+ /** Default maximum size of one file (`maxSize` of upload parameters). Default: `'10mb'`. */
345
+ readonly maxFileSize?: ByteSize;
346
+ /** Default maximum number of files of `@UploadedFiles` (and of multipart bodies of other routes). Default: `10`. */
347
+ readonly maxFiles?: number;
348
+ /** Maximum number of parts (text fields and files) of a multipart body (413 above). Default: `1000`. */
349
+ readonly maxParts?: number;
350
+ /** Maximum size of one text field of a multipart body (413 above). Default: `'1mb'`. */
351
+ readonly maxFieldSize?: ByteSize;
352
+ }
353
+ /**
354
+ * Reads uploaded files from the context (`ctx.files`) in generated `invoke` functions
355
+ * (`@UploadedFile('avatar') avatar: File` → `uploads.file(ctx, 'avatar')`). `field` `undefined`
356
+ * means any field. Limits were enforced while the body was read; these helpers check presence.
357
+ */
358
+ interface Uploads {
359
+ /** The file of `field`; 400 `Missing file "field"` when absent. */
360
+ file(ctx: HttpContext, field: string | undefined): File;
361
+ /** The file of `field`, `undefined` when absent. */
362
+ optionalFile(ctx: HttpContext, field: string | undefined): File | undefined;
363
+ /** The files of `field` (at least one); 400 `Missing files "field"` when none. */
364
+ files(ctx: HttpContext, field: string | undefined): File[];
365
+ /** The files of `field`, possibly none. */
366
+ optionalFiles(ctx: HttpContext, field: string | undefined): File[];
367
+ }
368
+ /** The helpers of the generated code (`import { uploads } from '@znight/http'`). */
369
+ declare const uploads: Uploads;
370
+ declare const requestIdBrand: unique symbol;
371
+ /** Why `ctx.signal` aborted: the client left, the server shuts down, or an SSE stream reached its `maxDuration`. */
372
+ type RequestAbortKind = "client" | "shutdown" | "expired";
373
+ /**
374
+ * The reason of an aborted `ctx.signal`. Named `AbortError`, what `fetch`, `node:timers/promises`
375
+ * and most libraries check.
376
+ */
377
+ declare class RequestAbortedError extends Error {
378
+ override readonly name = "AbortError";
379
+ /** `client`: disconnected; `shutdown`: the server stops; `expired`: an SSE stream reached its `maxDuration`. */
380
+ readonly kind: RequestAbortKind;
381
+ constructor(kind: RequestAbortKind);
382
+ }
383
+ /**
384
+ * Identifier of the current request: the incoming `x-request-id` header (see the plugin option
385
+ * `requestIdHeader`) or a generated UUID. Injectable in request-scoped providers.
386
+ */
387
+ type RequestId = string & {
388
+ readonly [requestIdBrand]: true;
389
+ };
390
+ /** The route that matched a request. */
391
+ interface HttpRouteInfo {
392
+ /** Route method (`GET`...; the concrete method for `@All` routes). */
393
+ readonly method: string;
394
+ /** Route pattern including the plugin prefix (`/api/users/:id`). */
395
+ readonly path: string;
396
+ /** Controller provider token. */
397
+ readonly controller: Token;
398
+ /** Handler method name. */
399
+ readonly handler: string;
400
+ /**
401
+ * Effective route metadata (`createRouteDecorator`): the controller's entries, except those of a
402
+ * group the route decides itself, then the route's entries (the nearest decision wins).
403
+ */
404
+ readonly meta: Readonly<Record<string, unknown>>;
405
+ }
406
+ declare module "@znight/core" {
407
+ /** Custom parameter decorators (`createParamDecorator`) resolve HTTP parameters from the {@link HttpContext}. */
408
+ interface ParamContexts {
409
+ http: HttpContext;
410
+ }
411
+ }
412
+ /**
413
+ * Adapter-neutral view of the current HTTP request, shared by guards, interceptors, exception
414
+ * mappers and handlers (`@Ctx()`), and injectable in request-scoped providers.
415
+ */
416
+ interface HttpContext {
417
+ readonly requestId: RequestId;
418
+ /** Upper-case method. */
419
+ readonly method: string;
420
+ /** Raw request target (`/users/a%20b?page=2`). */
421
+ readonly url: string;
422
+ /** Raw path, without query string (`/users/a%20b`). */
423
+ readonly path: string;
424
+ /** Decoded route parameters (`*` holds the part matched by a trailing wildcard). */
425
+ readonly params: Readonly<Record<string, string>>;
426
+ /** Decoded query string (repeated keys → arrays), parsed on first access. */
427
+ readonly query: Readonly<QueryRecord>;
428
+ /** Request headers, lower-case names. */
429
+ readonly headers: Readonly<Record<string, string | undefined>>;
430
+ /**
431
+ * Parsed body. Populated before the interceptors for routes declaring `@Body()` or upload
432
+ * parameters (and after {@link parseBody} resolved otherwise); `undefined` until then and for
433
+ * empty bodies. Forms (urlencoded and multipart) are records of their text fields.
434
+ */
435
+ readonly body: unknown;
436
+ /**
437
+ * Files of a `multipart/form-data` body by field name, in body order (standard `File` objects,
438
+ * held in memory). Populated with {@link body}; empty for other bodies.
439
+ */
440
+ readonly files: Readonly<Record<string, readonly File[]>>;
441
+ /** The matched route, `undefined` for unmatched requests (404/405). */
442
+ readonly route: HttpRouteInfo | undefined;
443
+ /** Per-request storage for guards, interceptors and handlers (`ctx.state.set('user', user)`). */
444
+ readonly state: Map<string | symbol, unknown>;
445
+ /** Native request of the adapter (`IncomingMessage`, `FastifyRequest`, Express `Request`, Web `Request`). */
446
+ readonly request: unknown;
447
+ /** Native response of the adapter (`ServerResponse`, `FastifyReply`, Express `Response`, Hono `Context`). */
448
+ readonly response: unknown;
449
+ /** Adapter name (`node`, `fastify`, `express`, `hono`). */
450
+ readonly adapter: string;
451
+ /**
452
+ * Client address, when the adapter knows it. With the plugin option `trustProxy`, the address
453
+ * found in `x-forwarded-for` behind the trusted proxies; otherwise the adapter's address (the
454
+ * socket peer, or what Fastify / Express derive from their own proxy settings).
455
+ */
456
+ readonly ip: string | undefined;
457
+ /**
458
+ * `https` when the connection uses TLS, or, behind a trusted proxy (`trustProxy`), when the
459
+ * leftmost `x-forwarded-proto` says so; `http` otherwise.
460
+ */
461
+ readonly protocol: "http" | "https";
462
+ /** Headers added to the response, whatever its outcome (errors included). */
463
+ readonly responseHeaders: Readonly<Record<string, HeaderValue>>;
464
+ /** A request header (case-insensitive name). */
465
+ header(name: string): string | undefined;
466
+ /** Sets a response header (case-insensitive name), sent with success and error responses. */
467
+ setHeader(name: string, value: HeaderValue): void;
468
+ /** Appends a response header value (`set-cookie`...). */
469
+ appendHeader(name: string, value: string): void;
470
+ /**
471
+ * Request cookies (`cookie` header), parsed on first access: decoded values, the first occurrence
472
+ * of a name wins. A prototype-less, frozen record.
473
+ */
474
+ readonly cookies: Readonly<Record<string, string>>;
475
+ /**
476
+ * The verified value of a signed cookie (`@SignedCookie`), `undefined` when it is absent, tampered
477
+ * or signed with a key that is no longer in `cookies.secrets`. Verified once per name.
478
+ *
479
+ * @throws Error when the plugin option `cookies.secrets` is not set.
480
+ */
481
+ signedCookie(name: string): string | undefined;
482
+ /**
483
+ * Sets a cookie on the response, whatever its outcome (errors, redirects included), with the
484
+ * secure defaults (`Path=/; Secure; HttpOnly; SameSite=Lax`, see the plugin option
485
+ * `cookies.defaults`). A later cookie with the same name, domain and path replaces this one; a
486
+ * cookie of the same key in an `HttpResponse` (`withCookie`) wins over it. Everything is validated
487
+ * now (name, value, attributes, prefixes, size), so the stack trace points to the faulty call.
488
+ *
489
+ * @throws TypeError / RangeError for an invalid cookie; Error once the response headers were sent
490
+ * (a running event stream).
491
+ */
492
+ setCookie(name: string, value: string, options?: CookieOptions): void;
493
+ /**
494
+ * Clears a cookie (`Max-Age=0`, with the same domain and path, and the defaults: a `__Host-`
495
+ * cookie is cleared with valid attributes).
496
+ *
497
+ * @throws like {@link setCookie}.
498
+ */
499
+ clearCookie(name: string, options?: ClearCookieOptions): void;
500
+ /** Cookies set on this response so far (`setCookie` / `clearCookie`), in order. */
501
+ readonly responseCookies: readonly ResponseCookie[];
502
+ /**
503
+ * Aborted when the client disconnects before the response was written, when the response body is
504
+ * cancelled, or when the server closes an event stream (shutdown, `maxDuration`); never after a
505
+ * normal completion. The reason is a {@link RequestAbortedError}. Pass it to the work the request
506
+ * starts (`fetch(url, { signal: ctx.signal })`, `setTimeout(ms, v, { signal })`). Created on first
507
+ * access.
508
+ */
509
+ readonly signal: AbortSignal;
510
+ /**
511
+ * Reads and parses the body (once): JSON (`application/json`, `+json`), text (`text/*`), forms
512
+ * (`application/x-www-form-urlencoded` and `multipart/form-data` → record of the text fields,
513
+ * repeated names as arrays; files in {@link files}), raw bytes otherwise; `undefined` when empty.
514
+ * Fails with 400 (malformed), 413 (body or upload limits) or 415 (unsupported encoding, refused
515
+ * file type, non-multipart body on an upload route).
516
+ */
517
+ parseBody(): Promise<unknown>;
518
+ /** The raw body bytes (once; shared with {@link parseBody}), e.g. to check a webhook signature. */
519
+ rawBody(): Promise<Uint8Array>;
520
+ /**
521
+ * The request body as a Web stream, read as it arrives (nothing is buffered): the escape hatch
522
+ * for large uploads (`PUT /files/:name` piped to disk or object storage). The body limit of the
523
+ * route still applies (`@BodyLimit`, `Infinity` to disable): past it the stream errors with a
524
+ * `PayloadTooLargeException` (413). Exclusive with {@link rawBody} / {@link parseBody}: throws when
525
+ * the body was already read (routes with `@Body()` or upload parameters read it before the
526
+ * handler).
527
+ */
528
+ bodyStream(): ReadableStream<Uint8Array>;
529
+ }
530
+ /** Route methods. `ALL` registers the route for GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS. */
531
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "ALL";
532
+ /**
533
+ * Where a handler parameter comes from (`file` / `files`: `@UploadedFile` / `@UploadedFiles`, the
534
+ * `name` being the multipart field; `cookie` / `signedCookie`: `@Cookie` / `@SignedCookie`, the
535
+ * `name` being the cookie name).
536
+ */
537
+ type HttpParamSource = "param" | "query" | "body" | "headers" | "request" | "response" | "context" | "file" | "files" | "cookie" | "signedCookie" | "custom";
538
+ /** A handler parameter (informational: extraction, coercion and validation happen in `invoke`). */
539
+ interface HttpParamDefinition {
540
+ /** Position in the handler signature. */
541
+ readonly index: number;
542
+ readonly source: HttpParamSource;
543
+ /** Parameter / query key / header name (lower case) / multipart field. Absent: the whole object (every field). */
544
+ readonly name?: string;
545
+ /** Serialised declared type (`number`, `string[]`, `'asc' | 'desc'`...). */
546
+ readonly type?: string;
547
+ readonly optional?: boolean;
548
+ /** `true` when the value is validated with a schema. */
549
+ readonly schema?: boolean;
550
+ }
551
+ /**
552
+ * Files accepted by an upload parameter (`@UploadedFile` / `@UploadedFiles`). The plugin enforces
553
+ * these rules while the multipart body is read; the presence of required files is checked by
554
+ * `invoke` (the `uploads` helpers).
555
+ */
556
+ interface HttpUploadDefinition {
557
+ /** Multipart field. Absent: files of any field. */
558
+ readonly field?: string;
559
+ /** `true` for `@UploadedFiles` (several files). */
560
+ readonly multiple?: boolean;
561
+ /** `true` when a file must be sent (informational, checked by `invoke`). */
562
+ readonly required?: boolean;
563
+ /** Maximum size of each file in bytes (`Infinity`: no limit). Default: plugin `uploads.maxFileSize`. */
564
+ readonly maxSize?: number;
565
+ /** Maximum number of files (`@UploadedFiles`). Default: plugin `uploads.maxFiles` (1 for `@UploadedFile`). */
566
+ readonly maxFiles?: number;
567
+ /** Accepted media types (`image/png`, `image/*`), compared with the type declared by the client. */
568
+ readonly mimeTypes?: readonly string[];
569
+ }
570
+ /** Options of an `@Sse` route (`HttpRouteDefinition.sse`). */
571
+ interface HttpSseDefinition {
572
+ /** Heartbeat interval in ms (≥ 1 000); `0` disables it. Absent: the plugin option `sse.heartbeat`. */
573
+ readonly heartbeat?: number;
574
+ /** `retry` sent first (ms). Absent: the plugin option `sse.retry`. */
575
+ readonly retry?: number;
576
+ /** The stream ends gracefully after this many ms (≥ 1 000). */
577
+ readonly maxDuration?: number;
578
+ }
579
+ /** A route: one decorated method of a controller. */
580
+ interface HttpRouteDefinition {
581
+ readonly method: HttpMethod;
582
+ /** Path of the route decorator, normalised (`/:id`). */
583
+ readonly path: string;
584
+ /** `joinPaths(controller.basePath, path)` (without the plugin `prefix`). */
585
+ readonly fullPath: string;
586
+ /** Name of the handler method. */
587
+ readonly handler: string;
588
+ /** `@HttpCode(status)`. */
589
+ readonly status?: number;
590
+ /** `@Header(name, value)` entries (names in lower case). */
591
+ readonly headers?: Readonly<Record<string, string>>;
592
+ /**
593
+ * Route metadata (`createRouteDecorator`), merged over the controller metadata at runtime, one
594
+ * decision per group: the route's entry of a group replaces the controller's entries of that group
595
+ * (see {@link HttpMetadata.groups}).
596
+ */
597
+ readonly meta: Readonly<Record<string, unknown>>;
598
+ /** Route guards (provider tokens), run after global and controller guards. */
599
+ readonly guards: readonly Token[];
600
+ /** Route interceptors (provider tokens), innermost. */
601
+ readonly interceptors: readonly Token[];
602
+ readonly params: readonly HttpParamDefinition[];
603
+ /**
604
+ * `@BodyLimit` (route or controller): maximum body size in bytes (`Infinity`: no limit). Default:
605
+ * the plugin `bodyLimit`, or for upload routes the sum of the file limits plus `bodyLimit`.
606
+ */
607
+ readonly bodyLimit?: number;
608
+ /**
609
+ * Upload rules (one per `@UploadedFile` / `@UploadedFiles` parameter). Routes with uploads parse
610
+ * `multipart/form-data` bodies before the interceptors and answer 415 to other non-empty bodies.
611
+ */
612
+ readonly uploads?: readonly HttpUploadDefinition[];
613
+ /** `true` when the handler takes `@Res()` and writes the response itself. */
614
+ readonly rawResponse?: boolean;
615
+ /**
616
+ * `@Sse` route (GET only): the handler result (an `AsyncIterable`, an `Iterable` or a
617
+ * `ReadableStream` of `SseEvent`) is served as an event stream; an `HttpResponse` or a Web
618
+ * `Response` is sent as usual. `{}` without options.
619
+ */
620
+ readonly sse?: HttpSseDefinition;
621
+ /**
622
+ * Provider classes resolving custom parameters (`ParamResolver`, RFC 0010), in order of first use:
623
+ * the plugin resolves them in the request scope and passes the instances to `invoke` as `x`
624
+ * (`x[i]` is the instance of `paramResolvers[i]`). Absent when the route has none.
625
+ */
626
+ readonly paramResolvers?: readonly Token[];
627
+ /**
628
+ * Calls the handler: extracts, coerces and validates every argument from the context (custom
629
+ * parameters included), then calls the method on the controller instance. May return a promise.
630
+ * `resolvers` is passed when the route has {@link paramResolvers}.
631
+ */
632
+ readonly invoke: (controller: any, ctx: HttpContext, resolvers?: any) => unknown;
633
+ }
634
+ /** A controller: a provider whose methods are routes. */
635
+ interface HttpControllerDefinition {
636
+ /** Provider token of the controller class. */
637
+ readonly token: Token;
638
+ /** Normalised base path (`/users`, `/` when absent). */
639
+ readonly basePath: string;
640
+ /** Controller guards (provider tokens). */
641
+ readonly guards: readonly Token[];
642
+ /** Controller interceptors (provider tokens). */
643
+ readonly interceptors: readonly Token[];
644
+ /** Controller metadata (`createRouteDecorator` on the class). */
645
+ readonly meta: Readonly<Record<string, unknown>>;
646
+ readonly routes: readonly HttpRouteDefinition[];
647
+ }
648
+ /** A route decorator group (`createRouteDecorator(key, { group, required })`). */
649
+ interface HttpRouteGroupDefinition {
650
+ /** Keys of the decorators of the group, sorted. */
651
+ readonly keys: readonly string[];
652
+ /** Every route must carry a decision of the group. */
653
+ readonly required: boolean;
654
+ }
655
+ /** `metadata.extensions.http`. */
656
+ interface HttpMetadata {
657
+ readonly controllers: readonly HttpControllerDefinition[];
658
+ /**
659
+ * Route decorator groups that need to be known at runtime (required, holding several keys, or
660
+ * named differently from their key): group → `{ keys, required }`. A key belongs to one group at
661
+ * most; a key listed nowhere is its own group. Absent when no group needs it.
662
+ */
663
+ readonly groups?: Readonly<Record<string, HttpRouteGroupDefinition>>;
664
+ }
665
+ declare module "@znight/core" {
666
+ interface ZnightExtensionsMetadata {
667
+ readonly http?: HttpMetadata;
668
+ }
669
+ }
670
+ /** How a file is presented to the client (`Content-Disposition`). */
671
+ type ContentDispositionType = "attachment" | "inline";
672
+ /** Options of `HttpResponse.file()`. */
673
+ interface FileResponseOptions {
674
+ /**
675
+ * Name proposed to the client in `Content-Disposition` (any Unicode name; path separators are
676
+ * removed). Setting it makes the response an `attachment` unless `disposition` says otherwise.
677
+ */
678
+ readonly filename?: string;
679
+ /**
680
+ * Media type of the response. Default: the type of the `Blob`, else looked up from the extension
681
+ * of `filename` / of the path (small built-in table, text types with `charset=utf-8`), else
682
+ * `application/octet-stream`.
683
+ */
684
+ readonly contentType?: string;
685
+ /**
686
+ * `attachment` (download) or `inline` (displayed by the browser). Default: `attachment` when
687
+ * `filename` is set, otherwise no `Content-Disposition` header. Without `filename`, the file name
688
+ * (path basename, `File.name`) is used.
689
+ */
690
+ readonly disposition?: ContentDispositionType;
691
+ /**
692
+ * Directory the path is resolved in. The resolved file (symbolic links followed) must stay inside
693
+ * it: `..` segments, absolute paths and links escaping the directory answer 404. Always set it
694
+ * when the path comes from the request.
695
+ */
696
+ readonly root?: string;
697
+ /**
698
+ * With `root`: whether files or directories whose name starts with a dot (`.env`, `.git/config`)
699
+ * may be sent. Default: `'deny'` (404).
700
+ */
701
+ readonly dotfiles?: "allow" | "deny";
702
+ }
703
+ /**
704
+ * The body of `HttpResponse.file()`: a file on disk or a `Blob`, resolved by the pipeline when the
705
+ * response is sent (stat, conditional request, range).
706
+ */
707
+ declare class FileBody {
708
+ /** Path (`string` / `file:` URL) or `Blob` / `File`. */
709
+ readonly source: string | URL | Blob;
710
+ readonly options: FileResponseOptions;
711
+ constructor(source: string | URL | Blob, options?: FileResponseOptions);
712
+ }
713
+ /** An inclusive byte range. */
714
+ interface ByteRange {
715
+ readonly start: number;
716
+ readonly end: number;
717
+ }
718
+ /**
719
+ * Parses a `Range` header for a representation of `size` bytes. Only a single `bytes` range is
720
+ * honoured: other units, several ranges and malformed values return `undefined` (the whole
721
+ * representation is sent, as RFC 9110 allows); a valid range starting after the end returns
722
+ * `'unsatisfiable'` (416).
723
+ */
724
+ declare function parseRange(header: string, size: number): ByteRange | "unsatisfiable" | undefined;
725
+ /**
726
+ * Builds a `Content-Disposition` header value (RFC 6266): `attachment; filename="report.pdf"`, with
727
+ * an RFC 8187 / 5987 `filename*` parameter for names that are not plain ASCII
728
+ * (`attachment; filename="r_sum_.pdf"; filename*=UTF-8''r%C3%A9sum%C3%A9.pdf`). Directory parts and
729
+ * control characters of `filename` are removed.
730
+ */
731
+ declare function contentDisposition(filename?: string, type?: ContentDispositionType): string;
732
+ /** One event of an event stream. */
733
+ interface SseEvent<T = unknown> {
734
+ /** A string is sent as is; anything else goes through `JSON.stringify` (`toJSON` honoured). */
735
+ readonly data?: T;
736
+ /** Event type (`addEventListener(type)`). The client defaults to `message`. No CR or LF. */
737
+ readonly event?: string;
738
+ /** Last event id, echoed by the client as `Last-Event-ID`. No CR, LF or NUL. `''` resets it. */
739
+ readonly id?: string | number;
740
+ /** Reconnection delay in ms (a safe integer ≥ 0). */
741
+ readonly retry?: number;
742
+ /** Comment lines, ignored by `EventSource` (debugging, padding). */
743
+ readonly comment?: string;
744
+ }
745
+ /** Options of `@Sse()` and `HttpResponse.sse()`. */
746
+ interface SseOptions {
747
+ /**
748
+ * Milliseconds without an event after which a keep-alive comment (`:`) is sent (an integer
749
+ * from 1 000 to 2 147 483 647). `false` disables it. Default: the plugin option `sse.heartbeat`
750
+ * (8 000).
751
+ */
752
+ readonly heartbeat?: number | false;
753
+ /**
754
+ * Client reconnection delay (`retry:`, ms), sent first. Default: the plugin option `sse.retry`
755
+ * (none: browsers use about 3 s).
756
+ */
757
+ readonly retry?: number;
758
+ /**
759
+ * The server ends the stream gracefully after this many ms (load rebalancing, re-authentication):
760
+ * `ctx.signal` aborts (`'expired'`), then the client gets a jittered `retry` and reconnects. An
761
+ * integer from 1 000 to 2 147 483 647 (about 24.8 days, the longest timer). Default: none.
762
+ */
763
+ readonly maxDuration?: number;
764
+ }
765
+ /**
766
+ * What `@Sse` handlers and `HttpResponse.sse()` accept: an async iterable (async generators,
767
+ * `EventChannel`, a Node.js `Readable` of raw bytes), an iterable (arrays, sync generators; not
768
+ * strings) or a `ReadableStream`, of `SseEvent` objects or `Uint8Array` chunks (already-framed SSE
769
+ * text, written as is: proxying an upstream event stream).
770
+ */
771
+ type SseSource = AsyncIterable<SseEvent | Uint8Array> | Iterable<SseEvent> | ReadableStream<SseEvent | Uint8Array>;
772
+ /** The body of `HttpResponse.sse()`: an event source and its options (served by the plugin). */
773
+ declare class SseBody {
774
+ readonly source: SseSource;
775
+ readonly options: SseOptions;
776
+ constructor(source: SseSource, options?: SseOptions);
777
+ }
778
+ /**
779
+ * Encodes one event (WHATWG framing): one `: <line>` per comment line, `id:`, `event:`, `retry:`,
780
+ * then one `data: <line>` per line of the data (split on CRLF, CR and LF, so data can never inject
781
+ * fields or end the event early), and an empty line. An `event` without `data` gets an empty
782
+ * `data:` line (otherwise `EventSource` would not dispatch it); events with only `id`, `retry` or
783
+ * `comment` have no data line.
784
+ *
785
+ * ```ts
786
+ * encodeSseEvent({ id: 41, event: 'status', data: { status: 'paid' } })
787
+ * // 'id: 41\nevent: status\ndata: {"status":"paid"}\n\n'
788
+ * ```
789
+ *
790
+ * @throws TypeError for anything but a plain object with the keys of {@link SseEvent} (strings,
791
+ * numbers, `null`: yield `{ data: value }`), an empty event, an `event` / `id` with CR or LF (NUL
792
+ * for `id`), a `retry` that is not an integer ≥ 0, a `data` JSON cannot serialise.
793
+ */
794
+ declare function encodeSseEvent(event: SseEvent): string;
795
+ /**
796
+ * A stream source accepted by `HttpResponse.stream()`: a Web `ReadableStream`, a Node.js `Readable`
797
+ * or any async iterable (async generator...) of `Uint8Array` / `string` chunks (strings are encoded
798
+ * as UTF-8).
799
+ */
800
+ type StreamSource = ReadableStream<Uint8Array | string> | AsyncIterable<Uint8Array | string>;
801
+ /** Options of {@link HttpResponse.stream}. */
802
+ interface StreamResponseOptions {
803
+ /** Media type. Default: from the extension of `filename`, else `application/octet-stream`. */
804
+ readonly contentType?: string;
805
+ /** Name proposed to the client (`Content-Disposition: attachment` unless `disposition` says otherwise). */
806
+ readonly filename?: string;
807
+ /** `attachment` or `inline`. Default: `attachment` when `filename` is set, otherwise no header. */
808
+ readonly disposition?: ContentDispositionType;
809
+ }
810
+ /** Options of {@link HttpResponse}. */
811
+ interface HttpResponseInit {
812
+ /** Status code. Default: the route default (`@HttpCode`, 201 for POST, 204 without body, 200). */
813
+ readonly status?: number;
814
+ /** Response headers (names are case-insensitive; arrays send the header several times). */
815
+ readonly headers?: Readonly<Record<string, HeaderValue>>;
816
+ /** Cookies to set (see {@link HttpResponse.withCookie}), in order. */
817
+ readonly cookies?: readonly ResponseCookie[];
818
+ }
819
+ /** Redirect statuses accepted by {@link HttpResponse.redirect}. */
820
+ type RedirectStatus = 301 | 302 | 303 | 307 | 308;
821
+ /**
822
+ * A handler result carrying a status and headers next to the body. The body is serialised with the
823
+ * usual rules (objects → JSON, strings → text, bytes and streams → octet-stream).
824
+ *
825
+ * ```ts
826
+ * @Post('/')
827
+ * async create(@Body(CreateUser) input: CreateUser): Promise<HttpResponse<User>> {
828
+ * const user = await this.users.create(input)
829
+ * return HttpResponse.created(user, `/users/${user.id}`)
830
+ * }
831
+ * ```
832
+ *
833
+ * Status precedence: `@HttpCode` > `HttpResponse.status` > route default.
834
+ */
835
+ declare class HttpResponse<T = unknown> {
836
+ #private;
837
+ readonly body: T;
838
+ readonly status: number | undefined;
839
+ readonly headers: Readonly<Record<string, HeaderValue>>;
840
+ /** Cookies set by this response (`withCookie` / `withoutCookie`), in order; they win over `ctx.setCookie()`. */
841
+ readonly cookies: readonly ResponseCookie[];
842
+ constructor(body: T, init?: HttpResponseInit);
843
+ /** `200 OK` with `body`. */
844
+ static ok<T>(body: T, headers?: Readonly<Record<string, HeaderValue>>): HttpResponse<T>;
845
+ /**
846
+ * `201 Created`, with a `location` header when given. With a body, the response is typed with it
847
+ * (`Promise<HttpResponse<BookResponse>>` type-checks).
848
+ */
849
+ static created(): HttpResponse<undefined>;
850
+ static created<T>(body: T, location?: string): HttpResponse<T>;
851
+ /** `202 Accepted`, typed with its body when there is one. */
852
+ static accepted(): HttpResponse<undefined>;
853
+ static accepted<T>(body: T): HttpResponse<T>;
854
+ /** `204 No Content`. */
855
+ static noContent(): HttpResponse<undefined>;
856
+ /** A redirect (`302 Found` by default) to `location`. */
857
+ static redirect(location: string, status?: RedirectStatus): HttpResponse<undefined>;
858
+ /**
859
+ * Sends a file from disk (`string` path or `file:` URL) or a `Blob` / `File`, streamed with
860
+ * `Content-Length`, `Content-Type` (from the extension, see `contentTypeFor`), `Last-Modified`,
861
+ * `ETag` and `Accept-Ranges`. Conditional requests answer 304 / 412 and single byte ranges 206
862
+ * (416 when unsatisfiable). A missing file answers 404.
863
+ *
864
+ * ```ts
865
+ * @Get('/reports/:name')
866
+ * download(@Param('name') name: string) {
867
+ * // `root` confines the path: '../../etc/passwd' answers 404
868
+ * return HttpResponse.file(name, { root: REPORTS_DIR, filename: `rapport ${name}` })
869
+ * }
870
+ * ```
871
+ *
872
+ * Never pass a path built from the request without `root`.
873
+ */
874
+ static file(file: string | URL | Blob, options?: FileResponseOptions): HttpResponse<FileBody>;
875
+ /**
876
+ * Streams a Web `ReadableStream`, a Node.js `Readable` or an async iterable (async generator) of
877
+ * `Uint8Array` / `string` chunks. The source is read as the client receives the data
878
+ * (backpressure) and cancelled when the client disconnects; it runs in the request scope.
879
+ *
880
+ * ```ts
881
+ * @Get('/export.csv')
882
+ * export() {
883
+ * return HttpResponse.stream(this.rows.csvLines(), { filename: 'export.csv' }) // text/csv; charset=utf-8
884
+ * }
885
+ * ```
886
+ */
887
+ static stream(source: StreamSource, options?: StreamResponseOptions): HttpResponse<ReadableStream<Uint8Array>>;
888
+ /**
889
+ * An event stream (Server-Sent Events) on any route and any method: `200` unless `@HttpCode` or
890
+ * {@link withStatus} say otherwise (`EventSource` requires 200; `fetch()` clients read any status),
891
+ * even on `POST` routes. Same source, framing and lifecycle as an `@Sse()` route.
892
+ *
893
+ * ```ts
894
+ * @Post('/:id/assistant')
895
+ * assistant(@Param('id') id: string, @Body(Prompt) prompt: Prompt, @Ctx() ctx: HttpContext) {
896
+ * return HttpResponse.sse(this.llm.complete(id, prompt, { signal: ctx.signal }))
897
+ * }
898
+ * ```
899
+ */
900
+ static sse(source: SseSource, options?: SseOptions): HttpResponse<SseBody>;
901
+ /** A copy of this response with `name` set to `value`. */
902
+ withHeader(name: string, value: HeaderValue): HttpResponse<T>;
903
+ /** A copy of this response with another status. */
904
+ withStatus(status: number): HttpResponse<T>;
905
+ /**
906
+ * A copy of this response setting a cookie, with the secure defaults (`Path=/; Secure; HttpOnly;
907
+ * SameSite=Lax`, see the plugin option `cookies.defaults`). It replaces a cookie with the same name,
908
+ * domain and path set by `ctx.setCookie()` or an earlier `withCookie()`.
909
+ *
910
+ * ```ts
911
+ * return HttpResponse.ok({ id: user.id }).withCookie('__Host-uid', user.id, { signed: true, maxAge: 8 * 3600 })
912
+ * ```
913
+ *
914
+ * The name, the value, the lifetime, the domain, the path and the size are checked now; the rules
915
+ * depending on the plugin (`secure` default with a prefix or `SameSite=None`, signing, the final
916
+ * size) when the response is sent: a violation then answers a generic 500, logged as
917
+ * `Invalid cookie "name": …`.
918
+ *
919
+ * @throws TypeError / RangeError for an invalid cookie.
920
+ */
921
+ withCookie(name: string, value: string, options?: CookieOptions): HttpResponse<T>;
922
+ /** A copy of this response clearing a cookie (see `ctx.clearCookie()`). */
923
+ withoutCookie(name: string, options?: ClearCookieOptions): HttpResponse<T>;
924
+ }
925
+ /** Whether `value` is an {@link HttpResponse} (also across duplicated package copies). */
926
+ declare function isHttpResponse(value: unknown): value is HttpResponse;
927
+ /**
928
+ * A guard decides whether a request may reach the handler. Guards run in order: global (plugin
929
+ * options) → controller (`@UseGuard` on the class) → route. Returning `false` responds
930
+ * `403 Forbidden`; throw an `HttpException` (e.g. `UnauthorizedException`) for another response.
931
+ *
932
+ * ```ts
933
+ * @Injectable()
934
+ * export class RolesGuard implements CanActivate {
935
+ * canActivate(ctx: HttpContext): boolean {
936
+ * const roles = Roles.get(ctx) // route metadata from createRouteDecorator
937
+ * return roles === undefined || roles.some(role => currentUser(ctx).roles.includes(role))
938
+ * }
939
+ * }
940
+ * ```
941
+ */
942
+ interface CanActivate {
943
+ canActivate(ctx: HttpContext): boolean | Promise<boolean>;
944
+ }
945
+ /** Runs the rest of the interceptor chain and the handler; resolves with the handler result. */
946
+ type CallHandler = () => Promise<unknown>;
947
+ /**
948
+ * Wraps the handler (after the guards). Interceptors run global → controller → route, the first one
949
+ * being the outermost. `next()` resolves with the handler result (before serialisation): return it,
950
+ * transform it, or return something else without calling `next()` (cache hit...).
951
+ *
952
+ * ```ts
953
+ * @Injectable()
954
+ * export class TimingInterceptor implements HttpInterceptor {
955
+ * async intercept(ctx: HttpContext, next: CallHandler) {
956
+ * const started = performance.now()
957
+ * try {
958
+ * return await next()
959
+ * } finally {
960
+ * ctx.setHeader('server-timing', `handler;dur=${performance.now() - started}`)
961
+ * }
962
+ * }
963
+ * }
964
+ * ```
965
+ */
966
+ interface HttpInterceptor {
967
+ intercept(ctx: HttpContext, next: CallHandler): unknown;
968
+ }
969
+ /** What an {@link ExceptionMapper} may return: `undefined` lets the next mapper (or the default) handle the error. */
970
+ type ExceptionMapperResult = HttpException | HttpResponse | Response | undefined;
971
+ /**
972
+ * Converts errors to responses. Mappers (plugin option `exceptionMappers`) are tried in order for
973
+ * every error of the pipeline, including `404`/`405`; the first one returning something wins. Return
974
+ * an `HttpException` to reuse the default error body, an `HttpResponse` or a Web `Response` for full
975
+ * control, `undefined` to pass.
976
+ *
977
+ * ```ts
978
+ * @Injectable()
979
+ * export class DomainErrorMapper implements ExceptionMapper {
980
+ * map(error: unknown) {
981
+ * if (error instanceof UserNotFoundError) return new NotFoundException(error.message)
982
+ * return undefined
983
+ * }
984
+ * }
985
+ * ```
986
+ */
987
+ interface ExceptionMapper {
988
+ map(error: unknown, ctx: HttpContext): ExceptionMapperResult | Promise<ExceptionMapperResult>;
989
+ }
990
+ /**
991
+ * Static side of a guard class: the route decorators whose decisions the guard enforces. Read by
992
+ * `HttpPlugin` at boot: a route whose decision belongs to a required group (or to a group some
993
+ * guard enforces) must have a guard enforcing that group in its chain (ZNIGHT023 otherwise).
994
+ *
995
+ * ```ts
996
+ * @Injectable()
997
+ * export class AuthGuard implements CanActivate {
998
+ * static readonly enforces = [Public, Authenticated, Roles]
999
+ * canActivate(ctx: HttpContext) { ... }
1000
+ * }
1001
+ * ```
1002
+ *
1003
+ * Typed with {@link RouteDecoratorInfo} (not `RouteDecorator<unknown>`) so that `[Public, Roles]`
1004
+ * stays assignable under `strictFunctionTypes`.
1005
+ */
1006
+ interface GuardEnforcement {
1007
+ readonly enforces?: readonly RouteDecoratorInfo[];
1008
+ }
1009
+ /** A class producing {@link CanActivate} instances (for `@UseGuard` and plugin options). */
1010
+ type GuardClass = (abstract new (...args: never[]) => CanActivate) & GuardEnforcement;
1011
+ /** A class producing {@link HttpInterceptor} instances. */
1012
+ type HttpInterceptorClass = abstract new (...args: never[]) => HttpInterceptor;
1013
+ /** A class producing {@link ExceptionMapper} instances. */
1014
+ type ExceptionMapperClass = abstract new (...args: never[]) => ExceptionMapper;
1015
+ /** Options of {@link Controller} (static). */
1016
+ interface ControllerOptions {
1017
+ /** Base path of every route of the controller (`/users`). Default: `/`. */
1018
+ readonly path?: string;
1019
+ /** Provider scope. Default: `singleton`; `request` creates one controller per request. */
1020
+ readonly scope?: Scope;
1021
+ }
1022
+ /**
1023
+ * Declares a controller: a provider (singleton by default) whose decorated methods are routes.
1024
+ *
1025
+ * ```ts
1026
+ * @Controller('/users')
1027
+ * export class UserController {
1028
+ * constructor(private readonly users: UserService) {}
1029
+ *
1030
+ * @Get('/:id')
1031
+ * find(@Param('id') id: number) {
1032
+ * return this.users.findById(id)
1033
+ * }
1034
+ * }
1035
+ * ```
1036
+ *
1037
+ * @param pathOrOptions static: base path, or `{ path?, scope? }`.
1038
+ */
1039
+ declare function Controller(_pathOrOptions?: string | ControllerOptions): ClassMarker;
1040
+ /** `GET` route. `path` (static) is relative to the controller path; default: the controller path. */
1041
+ declare function Get(_path?: string): MethodMarker;
1042
+ /** `POST` route (default status 201). */
1043
+ declare function Post(_path?: string): MethodMarker;
1044
+ /** `PUT` route. */
1045
+ declare function Put(_path?: string): MethodMarker;
1046
+ /** `PATCH` route. */
1047
+ declare function Patch(_path?: string): MethodMarker;
1048
+ /** `DELETE` route. */
1049
+ declare function Delete(_path?: string): MethodMarker;
1050
+ /** `HEAD` route (`GET` routes already answer `HEAD` requests). */
1051
+ declare function Head(_path?: string): MethodMarker;
1052
+ /** `OPTIONS` route (paths without one answer `OPTIONS` with `204` and an `allow` header). */
1053
+ declare function Options(_path?: string): MethodMarker;
1054
+ /** Route for GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS (methods without a dedicated route on the same path). */
1055
+ declare function All(_path?: string): MethodMarker;
1056
+ /**
1057
+ * A `GET` route serving an event stream (Server-Sent Events). The handler returns an
1058
+ * `AsyncIterable`, an `Iterable` or a `ReadableStream` of `SseEvent` (`{ data, event, id, retry,
1059
+ * comment }`), or an `HttpResponse` / exception for other outcomes (204 stops `EventSource`). The
1060
+ * plugin frames the events, sends heartbeats, stops the source when the client leaves (`ctx.signal`)
1061
+ * and closes the stream gracefully on shutdown.
1062
+ *
1063
+ * ```ts
1064
+ * @Sse('/:id/events', { retry: 5_000 })
1065
+ * async events(@Param('id') id: string, @Ctx() ctx: HttpContext): Promise<AsyncIterable<SseEvent<OrderEvent>>> {
1066
+ * if ((await this.orders.find(id)) === undefined) throw new NotFoundException() // pre-flight checks first
1067
+ * return this.hub.subscribe(id, { signal: ctx.signal })
1068
+ * }
1069
+ * ```
1070
+ *
1071
+ * Prefer an `async` method returning the source over an `async *` generator: the body of a
1072
+ * generator only runs once the 200 and the headers are sent.
1073
+ *
1074
+ * @param path static: route path (relative to the controller path).
1075
+ * @param options static: `{ heartbeat?, retry?, maxDuration? }` (see `SseOptions`).
1076
+ */
1077
+ declare function Sse(path?: string, options?: SseOptions): MethodMarker;
1078
+ declare function Sse(options: SseOptions): MethodMarker;
1079
+ /**
1080
+ * Injects a route parameter, coerced to the parameter type (`string`, `number`, `boolean`,
1081
+ * `bigint`, `Date`, literal unions) or validated with `schema`. Without arguments: every parameter
1082
+ * (`Record<string, string>`).
1083
+ *
1084
+ * ```ts
1085
+ * @Get('/:id')
1086
+ * find(@Param('id') id: number) {}
1087
+ * ```
1088
+ *
1089
+ * @param name static: name of a `:param` of the route path.
1090
+ * @param schema value: a Standard Schema validating the raw string (its output is injected).
1091
+ */
1092
+ declare function Param(): ParameterMarker;
1093
+ declare function Param(name: string, schema?: StandardSchemaV1): ParameterMarker;
1094
+ declare function Param(schema: StandardSchemaV1): ParameterMarker;
1095
+ /**
1096
+ * Injects a query parameter (coerced to the parameter type; `T[]` accepts repeated and single
1097
+ * values; optional when the parameter is optional or has a default) or, without name, the whole
1098
+ * query (`Record<string, string | string[]>`), typically validated with a schema.
1099
+ *
1100
+ * ```ts
1101
+ * list(@Query('page') page = 1, @Query('tag') tags: string[] = []) {}
1102
+ * search(@Query(SearchQuery) query: SearchQuery) {}
1103
+ * ```
1104
+ *
1105
+ * @param name static: query key.
1106
+ * @param schema value: a Standard Schema (its output is injected).
1107
+ */
1108
+ declare function Query(): ParameterMarker;
1109
+ declare function Query(name: string, schema?: StandardSchemaV1): ParameterMarker;
1110
+ declare function Query(schema: StandardSchemaV1): ParameterMarker;
1111
+ /**
1112
+ * Injects a request cookie (decoded, coerced to the parameter type like `@Headers`: `string`,
1113
+ * `number`, `boolean`, `bigint`, `Date`, literal unions, enums, branded primitives) or validated
1114
+ * with `schema`; without name, every cookie (`Record<string, string>`). An absent cookie answers 400
1115
+ * `Missing cookie "name"` unless the parameter is optional. Error messages never echo the value.
1116
+ *
1117
+ * ```ts
1118
+ * // a stale or forged value falls back instead of answering 400 until the cookie expires
1119
+ * export const Theme = z.enum(['light', 'dark']).catch('light')
1120
+ *
1121
+ * read(@Cookie('theme', Theme) theme: 'light' | 'dark', @Cookie('visits') visits = 0) {}
1122
+ * ```
1123
+ *
1124
+ * @param name static: cookie name (an RFC 9110 token).
1125
+ * @param schema value: a Standard Schema (its output is injected).
1126
+ */
1127
+ declare function Cookie(): ParameterMarker;
1128
+ declare function Cookie(name: string, schema?: StandardSchemaV1): ParameterMarker;
1129
+ declare function Cookie(schema: StandardSchemaV1): ParameterMarker;
1130
+ /**
1131
+ * Injects the **verified** value of a signed cookie (set with `{ signed: true }`, plugin option
1132
+ * `cookies.secrets`): absent, tampered or signed with a retired key, the cookie does not exist
1133
+ * (400 `Missing cookie "name"` unless the parameter is optional). Same coercion and schema rules as
1134
+ * {@link Cookie}. A route reading a signed cookie fails the boot when `cookies.secrets` is not set.
1135
+ *
1136
+ * @param name static: cookie name.
1137
+ * @param schema value: a Standard Schema (its output is injected).
1138
+ */
1139
+ declare function SignedCookie(_name: string, _schema?: StandardSchemaV1): ParameterMarker;
1140
+ /**
1141
+ * Injects the parsed request body, validated with `schema` when given (the schema output is
1142
+ * injected: defaults, transforms, stripped keys). A non-primitive body without schema is reported
1143
+ * by the compiler (ZNIGHT017).
1144
+ *
1145
+ * @param schema value: a Standard Schema (Zod, Valibot, ArkType...).
1146
+ */
1147
+ declare function Body(_schema?: StandardSchemaV1): ParameterMarker;
1148
+ /**
1149
+ * Injects a request header (case-insensitive `name`, coerced to the parameter type) or, without
1150
+ * name, every header (`Record<string, string | undefined>`).
1151
+ *
1152
+ * @param name static: header name.
1153
+ */
1154
+ declare function Headers(_name?: string): ParameterMarker;
1155
+ /**
1156
+ * Injects a file of a `multipart/form-data` body as a standard `File` (`name`, `type`, `size`,
1157
+ * `arrayBuffer()`, `text()`, `stream()`), held in memory. The route parses multipart bodies before
1158
+ * its interceptors, enforcing the options while the body is read: a file above `maxSize` answers
1159
+ * 413, a refused type 415, a second file in the field or a file in an undeclared field 400, a
1160
+ * missing required file 400, a non-multipart body 415. `@Body()` receives the text fields.
1161
+ *
1162
+ * ```ts
1163
+ * @Post('/avatar')
1164
+ * upload(@UploadedFile('avatar', { maxSize: '2mb', mimeTypes: ['image/png', 'image/jpeg'] }) avatar: File) {}
1165
+ *
1166
+ * @Post('/attachment')
1167
+ * attach(@UploadedFile('file') file?: File) {} // optional: undefined when absent
1168
+ * ```
1169
+ *
1170
+ * `File.name` and `File.type` are sent by the client: never trust them (see the README, "Files").
1171
+ *
1172
+ * @param field static: the multipart field; omitted: the single file of the body, whatever its field.
1173
+ * @param options static: `{ required?, maxSize?, mimeTypes? }`.
1174
+ */
1175
+ declare function UploadedFile(field?: string, options?: UploadedFileOptions): ParameterMarker;
1176
+ declare function UploadedFile(options: UploadedFileOptions): ParameterMarker;
1177
+ /**
1178
+ * Injects the files of a multipart field (`File[]`, in body order), or of every field without
1179
+ * `field`. Same rules as {@link UploadedFile}; above `maxFiles` files the request answers 413. An
1180
+ * optional parameter (`files?: File[]`, `required: false`) receives an empty array when no file is
1181
+ * sent.
1182
+ *
1183
+ * ```ts
1184
+ * @Post('/documents')
1185
+ * add(@UploadedFiles('documents', { maxFiles: 5, maxSize: '20mb', mimeTypes: ['application/pdf'] }) documents: File[]) {}
1186
+ * ```
1187
+ *
1188
+ * @param field static: the multipart field; omitted: the files of every field.
1189
+ * @param options static: `{ required?, maxSize?, maxFiles?, mimeTypes? }`.
1190
+ */
1191
+ declare function UploadedFiles(field?: string, options?: UploadedFilesOptions): ParameterMarker;
1192
+ declare function UploadedFiles(options: UploadedFilesOptions): ParameterMarker;
1193
+ /**
1194
+ * Sets the maximum request body size of a route, or of every route of a controller (the route wins),
1195
+ * over the plugin `bodyLimit` (and over the limit computed for upload routes). `Infinity` disables
1196
+ * the limit, e.g. for a route streaming its body with `ctx.bodyStream()`.
1197
+ *
1198
+ * ```ts
1199
+ * @Put('/files/:name')
1200
+ * @BodyLimit('5gb')
1201
+ * async store(@Param('name') name: string, @Ctx() ctx: HttpContext) {
1202
+ * await pipeline(Readable.fromWeb(ctx.bodyStream()), createWriteStream(join(DIR, safeName(name))))
1203
+ * }
1204
+ * ```
1205
+ *
1206
+ * @param size static: bytes (`10_485_760`) or a size with a unit (`'10mb'`, `'512kb'`; `kb` = 1024 bytes).
1207
+ */
1208
+ declare function BodyLimit(_size: ByteSize): ClassMarker & MethodMarker;
1209
+ /** Injects the native request of the adapter (escape hatch; the compiler warns). */
1210
+ declare function Req(): ParameterMarker;
1211
+ /**
1212
+ * Injects the native response of the adapter (escape hatch; the compiler warns). The handler then
1213
+ * writes the response itself, and must have finished when its promise settles.
1214
+ */
1215
+ declare function Res(): ParameterMarker;
1216
+ /** Injects the {@link HttpContext} of the request. */
1217
+ declare function Ctx(): ParameterMarker;
1218
+ /**
1219
+ * Sets the status of successful responses (wins over `HttpResponse.status` and the defaults).
1220
+ *
1221
+ * @param status static: an integer between 100 and 599.
1222
+ */
1223
+ declare function HttpCode(_status: number): MethodMarker;
1224
+ /**
1225
+ * Adds a header to successful responses. Repeat the decorator for several headers.
1226
+ *
1227
+ * @param name static: header name.
1228
+ * @param value static: header value.
1229
+ */
1230
+ declare function Header(_name: string, _value: string): MethodMarker;
1231
+ /**
1232
+ * Protects a controller (every route) or a route with guards (providers implementing
1233
+ * {@link CanActivate}). Order: global guards → controller guards → route guards, each list in
1234
+ * declaration order.
1235
+ *
1236
+ * @param guards value: guard classes.
1237
+ */
1238
+ declare function UseGuard(..._guards: [GuardClass, ...GuardClass[]]): ClassMarker & MethodMarker;
1239
+ /**
1240
+ * Wraps a controller (every route) or a route with interceptors (providers implementing
1241
+ * {@link HttpInterceptor}). Order: global → controller → route, the first one being the outermost.
1242
+ *
1243
+ * @param interceptors value: interceptor classes.
1244
+ */
1245
+ declare function UseInterceptor(..._interceptors: [HttpInterceptorClass, ...HttpInterceptorClass[]]): ClassMarker & MethodMarker;
1246
+ /**
1247
+ * Validates the request body with `schema`: sugar for `@Body(schema)` on the body parameter (using
1248
+ * both is ZNIGHT005).
1249
+ *
1250
+ * @param schema value: a Standard Schema.
1251
+ */
1252
+ declare function Validate(_schema: StandardSchemaV1): MethodMarker;
1253
+ /** Arguments of a route decorator: optional when `T` accepts `undefined` or is `true` (flag decorators). */
1254
+ type RouteDecoratorArgs<T> = [T] extends [true] ? [value?: T] : undefined extends T ? [value?: T] : [value: T];
1255
+ /** Options of {@link createRouteDecorator} (static). */
1256
+ interface RouteDecoratorOptions {
1257
+ /**
1258
+ * Decision group. Decorators of a group are alternatives: one per controller or route, and the
1259
+ * route's decision replaces the controller's. Default: the key (the decorator is its own group).
1260
+ * A letter followed by letters, digits, `_`, `.` or `-`.
1261
+ */
1262
+ readonly group?: string;
1263
+ /**
1264
+ * Every route must carry a decision of the group: ZNIGHT023 at build time when a route has none,
1265
+ * and at boot when no guard of its chain enforces it (see {@link GuardClass}). All the decorators
1266
+ * of a group must agree. Default `false`.
1267
+ */
1268
+ readonly required?: boolean;
1269
+ }
1270
+ /** The data part of a route decorator: what guards list in `static enforces`. */
1271
+ interface RouteDecoratorInfo {
1272
+ /** Key of the value in `route.meta`. */
1273
+ readonly key: string;
1274
+ /** Decision group (`options.group ?? key`). */
1275
+ readonly group: string;
1276
+ /** Every route must carry a decision of the group. */
1277
+ readonly required: boolean;
1278
+ }
1279
+ /**
1280
+ * A route metadata entry given as data rather than as a decorator: the `meta` of plugin routes
1281
+ * (`[Public.meta()]`). Built by {@link RouteDecorator.meta}.
1282
+ */
1283
+ interface RouteMetaEntry<T = unknown> {
1284
+ readonly key: string;
1285
+ readonly value: T;
1286
+ /** Decision group. Default: the key. */
1287
+ readonly group?: string;
1288
+ /** Default `false`. */
1289
+ readonly required?: boolean;
1290
+ }
1291
+ /**
1292
+ * Anything that carries effective route metadata: the {@link HttpContext} of a request, and the
1293
+ * contexts of other transports that reuse route decorators (a GraphQL resolver context, a WebSocket
1294
+ * gateway context). {@link RouteDecorator.get} reads `route.meta[key]`; nothing else is required.
1295
+ *
1296
+ * ```ts
1297
+ * const carrier: RouteMetaCarrier = { route: { meta: { roles: ['admin'] } } }
1298
+ * Roles.get(carrier) // ['admin']
1299
+ * ```
1300
+ */
1301
+ interface RouteMetaCarrier {
1302
+ /** The matched route (or field, or handler), `undefined` when there is none. */
1303
+ readonly route?: {
1304
+ readonly meta: Readonly<Record<string, unknown>>;
1305
+ } | undefined;
1306
+ }
1307
+ /** A user-defined route metadata decorator created with {@link createRouteDecorator}. */
1308
+ interface RouteDecorator<T> extends RouteDecoratorInfo {
1309
+ (...args: RouteDecoratorArgs<T>): ClassMarker & MethodMarker;
1310
+ /**
1311
+ * The value of the current route, if set. Reads the effective metadata: the route's decision of
1312
+ * a group wins over the controller's. Accepts any {@link RouteMetaCarrier}: an
1313
+ * {@link HttpContext}, or the context of another transport exposing `route.meta`.
1314
+ */
1315
+ get(ctx: RouteMetaCarrier): T | undefined;
1316
+ /**
1317
+ * The same metadata as data (`{ key, value, group, required }`), for routes that are not compiled
1318
+ * controllers (plugin routes: `OpenApiPlugin.configure({ meta: [Public.meta()] })`). The value is
1319
+ * `true` when omitted, like `@Public()`.
1320
+ */
1321
+ meta(...args: RouteDecoratorArgs<T>): RouteMetaEntry<T>;
1322
+ }
1323
+ /**
1324
+ * Creates a metadata decorator for controllers and routes. The compiler stores its argument
1325
+ * (`value`, statically evaluated or an exported reference; `true` when omitted) in
1326
+ * `meta[key]`, read by guards and interceptors through `ctx.route.meta` or `decorator.get(ctx)`.
1327
+ *
1328
+ * ```ts
1329
+ * export const Public = createRouteDecorator('public', { group: 'access', required: true })
1330
+ * export const Authenticated = createRouteDecorator('authenticated', { group: 'access', required: true })
1331
+ * export const Roles = createRouteDecorator<readonly string[]>('roles', { group: 'access', required: true })
1332
+ *
1333
+ * @Controller('/books')
1334
+ * @Roles(['reader']) // the decision of every route of the controller...
1335
+ * export class BookController {
1336
+ * @Get('/:id/cover')
1337
+ * @Public() // ...unless the route has its own decision of the group
1338
+ * cover() {}
1339
+ * }
1340
+ *
1341
+ * // in a guard
1342
+ * if (Public.get(ctx)) return true
1343
+ * const roles = Roles.get(ctx) ?? []
1344
+ * ```
1345
+ *
1346
+ * Decorators of one `group` are alternatives: two of them on the same class or method are
1347
+ * ZNIGHT005, and the route's decision replaces the controller's (without `group`, each decorator is
1348
+ * its own group: the route value wins over the controller value). With `required: true`, a route
1349
+ * without decision fails the build (ZNIGHT023).
1350
+ *
1351
+ * @param key static: unique metadata key.
1352
+ * @param options static: `{ group?, required? }`.
1353
+ */
1354
+ declare function createRouteDecorator<T = true>(key: string, options?: RouteDecoratorOptions): RouteDecorator<T>;
1355
+ /**
1356
+ * Origins allowed to read responses: `'*'` (any origin), an exact origin
1357
+ * (`'https://app.example.com'`), an anchored `RegExp` (`/^https:\/\/[a-z0-9-]+\.example\.com$/`), a
1358
+ * list of those, or a function (awaited; a function that throws or rejects denies the origin).
1359
+ */
1360
+ type CorsOrigin = "*" | string | RegExp | readonly (string | RegExp)[] | ((origin: string) => boolean | Promise<boolean>);
1361
+ /** The `cors` option of `HttpPlugin.configure`. */
1362
+ interface CorsOptions {
1363
+ /** Allowed origins (required). */
1364
+ readonly origin: CorsOrigin;
1365
+ /** `access-control-allow-credentials: true` (cookies, `Authorization`). Not with `'*'`. Default `false`. */
1366
+ readonly credentials?: boolean;
1367
+ /**
1368
+ * `access-control-allow-methods` of preflights. Default: the methods routed on the requested path.
1369
+ * `'*'` (any method) is refused with `credentials`, where browsers read it as a method named `*`.
1370
+ */
1371
+ readonly methods?: readonly string[];
1372
+ /**
1373
+ * `access-control-allow-headers` of preflights. Default: the headers the browser asks for
1374
+ * (`access-control-request-headers`). `'*'` is refused with `credentials`; it covers
1375
+ * `Authorization` too (sent as `*, authorization` when the browser asks for it: browsers never let
1376
+ * `*` alone cover it).
1377
+ */
1378
+ readonly allowHeaders?: readonly string[] | "*";
1379
+ /**
1380
+ * `access-control-expose-headers` of replies. Default: the request id header (none when it is
1381
+ * `false`). `'*'` (every header) is refused with `credentials`, where browsers read it literally.
1382
+ */
1383
+ readonly exposeHeaders?: readonly string[];
1384
+ /** `access-control-max-age` of preflights, in seconds (0 to 86400). Default `600`. */
1385
+ readonly maxAge?: number;
1386
+ }
1387
+ /**
1388
+ * Client side of Server-Sent Events: the WHATWG "interpreting an event stream" algorithm over a
1389
+ * byte stream (`parseSseStream`), used by `injectSse()` and by tests over real sockets. Import-free.
1390
+ */
1391
+ /** A dispatched event. */
1392
+ interface SseMessage {
1393
+ /** Event type: `message` by default. */
1394
+ readonly event: string;
1395
+ readonly data: string;
1396
+ /** The last event id at dispatch time (what `EventSource` sends back as `Last-Event-ID`). */
1397
+ readonly id: string;
1398
+ /** `JSON.parse(data)`. */
1399
+ json<T = unknown>(): T;
1400
+ }
1401
+ /** An item of an event stream: an event, a comment (heartbeats are `''`) or a reconnection delay. */
1402
+ type SseItem = ({
1403
+ readonly type: "event";
1404
+ } & SseMessage) | {
1405
+ readonly type: "comment";
1406
+ readonly text: string;
1407
+ } | {
1408
+ readonly type: "retry";
1409
+ readonly retry: number;
1410
+ };
1411
+ /** Options of {@link parseSseStream}. */
1412
+ interface ParseSseOptions {
1413
+ /** Stops reading (the body is cancelled) when aborted. */
1414
+ readonly signal?: AbortSignal;
1415
+ }
1416
+ /**
1417
+ * Turns an event stream body (a `fetch()` response body, `injectStream().body`) into its items.
1418
+ * UTF-8 is decoded with the BOM stripped; an unterminated event at the end is discarded. Calling
1419
+ * `return()` (leaving a `for await` loop) or aborting `signal` cancels the body.
1420
+ *
1421
+ * ```ts
1422
+ * const response = await fetch(`${http.address}/orders/o-1/events`)
1423
+ * for await (const item of parseSseStream(response.body!)) {
1424
+ * if (item.type === 'event') console.log(item.event, item.json())
1425
+ * }
1426
+ * ```
1427
+ */
1428
+ declare function parseSseStream(body: ReadableStream<Uint8Array>, options?: ParseSseOptions): AsyncIterableIterator<SseItem>;
1429
+ /** Options of {@link InjectSseResponse.next} and {@link InjectSseResponse.take}. */
1430
+ interface InjectSseWaitOptions {
1431
+ /** Maximum wait for each event (ms). Default: 2 000. */
1432
+ readonly timeoutMs?: number;
1433
+ }
1434
+ /** The response of `injectSse()`: an event stream read in memory, like `EventSource` would. */
1435
+ interface InjectSseResponse extends AsyncIterable<SseMessage>, AsyncDisposable {
1436
+ readonly status: number;
1437
+ readonly headers: Record<string, string | string[]>;
1438
+ readonly cookies: Readonly<Record<string, SetCookieInfo>>;
1439
+ /** `''` for an event stream; otherwise the whole body, read to its end (an error, a 204...). */
1440
+ readonly body: string;
1441
+ /** `JSON.parse(body)`. */
1442
+ json<T = unknown>(): T;
1443
+ /** The next event (comments skipped). Rejects on timeout (default 2 000 ms) or when the stream ended. */
1444
+ next(options?: InjectSseWaitOptions): Promise<SseMessage>;
1445
+ /** The next `count` events (the timeout applies to each). */
1446
+ take(count: number, options?: InjectSseWaitOptions): Promise<SseMessage[]>;
1447
+ /** Comments received so far (heartbeats are `''`). */
1448
+ readonly comments: readonly string[];
1449
+ /** What a browser would send as `Last-Event-ID` on reconnection. */
1450
+ readonly lastEventId: string;
1451
+ /** The last `retry` received (ms). */
1452
+ readonly retry: number | undefined;
1453
+ /** Resolves when the server ends the stream (reading the remaining events, which stay available to `next()`). */
1454
+ readonly done: Promise<void>;
1455
+ /** Disconnects (cancels the body): the server sees a client disconnect. Idempotent. */
1456
+ close(): Promise<void>;
1457
+ /** Reconnects like `EventSource`: closes this stream and sends the same request plus `last-event-id`. */
1458
+ reconnect(): Promise<InjectSseResponse>;
1459
+ }
1460
+ /**
1461
+ * The `trustProxy` option: `false` (default: the headers are ignored), a number of trusted hops
1462
+ * (`1`: one load balancer in front of the server), or the addresses of the trusted proxies (IPv4 /
1463
+ * IPv6 addresses, CIDR ranges and the aliases `'loopback'`, `'private'`, `'linklocal'`).
1464
+ */
1465
+ type TrustProxy = false | number | readonly string[];
1466
+ /** HSTS settings (`strict-transport-security`). */
1467
+ interface HstsOptions {
1468
+ /** `max-age`, in seconds. Default `31536000` (one year). */
1469
+ readonly maxAge?: number;
1470
+ /** `includeSubDomains`. Default `true`. */
1471
+ readonly includeSubDomains?: boolean;
1472
+ /** `preload` (needs `includeSubDomains` and a `maxAge` of at least one year). Default `false`. */
1473
+ readonly preload?: boolean;
1474
+ }
1475
+ /** The `securityHeaders` option of `HttpPlugin.configure` (merged over the defaults). */
1476
+ interface SecurityHeadersOptions {
1477
+ /** `content-security-policy`. Default `"default-src 'none'; frame-ancestors 'none'"`. */
1478
+ readonly contentSecurityPolicy?: string | false;
1479
+ /** `x-frame-options`. Default `'DENY'`. */
1480
+ readonly frameOptions?: "DENY" | "SAMEORIGIN" | false;
1481
+ /** `x-content-type-options: nosniff`. Default `true`. */
1482
+ readonly noSniff?: boolean;
1483
+ /** `referrer-policy` (a policy or a comma-separated fallback list). Default `'no-referrer'`. */
1484
+ readonly referrerPolicy?: string | false;
1485
+ /** `strict-transport-security`. Default `false`; `true`: `max-age=31536000; includeSubDomains`. */
1486
+ readonly hsts?: boolean | HstsOptions;
1487
+ /** `cross-origin-resource-policy`. Default `false`. */
1488
+ readonly crossOriginResourcePolicy?: "same-origin" | "same-site" | "cross-origin" | false;
1489
+ /** `cache-control` of replies that do not set one (`'no-store'` for personal data). Default `false`. */
1490
+ readonly cacheControl?: string | false;
1491
+ }
1492
+ /** How the boot check reports problems. */
1493
+ type EnforcementMode = "error" | "warn";
1494
+ /** How requests whose path ends with an extra `/` are handled. */
1495
+ type TrailingSlashPolicy = "strict" | "ignore" | "redirect";
1496
+ /** A global guard: a provider class (resolved with the container) or an instance. */
1497
+ type GuardRef = GuardClass | CanActivate;
1498
+ /** A global interceptor: a provider class or an instance. */
1499
+ type InterceptorRef = HttpInterceptorClass | HttpInterceptor;
1500
+ /** An exception mapper: a provider class or an instance. */
1501
+ type ExceptionMapperRef = ExceptionMapperClass | ExceptionMapper;
1502
+ /** Options of {@link HttpPlugin.configure}. */
1503
+ interface HttpPluginOptions {
1504
+ /** Server implementation. Default: `nodeAdapter()` (built-in, `node:http`). */
1505
+ readonly adapter?: HttpAdapter;
1506
+ /** Port to listen on (`0` picks a free port). Default: `3000`. */
1507
+ readonly port?: number;
1508
+ /** Interface to bind. Default: `0.0.0.0`. */
1509
+ readonly host?: string;
1510
+ /** Path prefix of every route (`/api`). Default: none. */
1511
+ readonly prefix?: string;
1512
+ /** Guards run before the controller and route guards of every route. */
1513
+ readonly guards?: readonly GuardRef[];
1514
+ /** Interceptors wrapping every route (outermost first). */
1515
+ readonly interceptors?: readonly InterceptorRef[];
1516
+ /** Error-to-response mappers, tried in order (see `ExceptionMapper`). */
1517
+ readonly exceptionMappers?: readonly ExceptionMapperRef[];
1518
+ /**
1519
+ * `strict` (default): `/users/` does not match `/users`. `ignore`: it does. `redirect`: it is
1520
+ * redirected (308) to `/users`.
1521
+ */
1522
+ readonly trailingSlash?: TrailingSlashPolicy;
1523
+ /** Send the message of unexpected errors in 500 responses (development only). Default: `false`. */
1524
+ readonly exposeErrors?: boolean;
1525
+ /**
1526
+ * Maximum request body size (413 above): bytes or a size with a unit (`'10mb'`; `kb` = 1024
1527
+ * bytes), `Infinity` for no limit. Routes override it with `@BodyLimit`; upload routes default to
1528
+ * the sum of their file limits plus this value. Default: 1 MiB.
1529
+ */
1530
+ readonly bodyLimit?: ByteSize;
1531
+ /** Limits of multipart bodies and defaults of the upload parameters (see `UploadLimits`). */
1532
+ readonly uploads?: UploadLimits;
1533
+ /**
1534
+ * Header carrying the request id: reused when the client sends a valid one (≤ 128 characters of
1535
+ * `[A-Za-z0-9_-.:@/+=]`), echoed on every response. `false`: always generate, never echo.
1536
+ * Default: `x-request-id`.
1537
+ */
1538
+ readonly requestIdHeader?: string | false;
1539
+ /** Request id generator. Default: `crypto.randomUUID()`. */
1540
+ readonly generateRequestId?: () => string;
1541
+ /**
1542
+ * Maximum time `onStop` waits for in-flight requests before closing the remaining connections.
1543
+ * Keep it below the application `shutdown.hookTimeoutMs`. `Infinity`: no limit of its own (the
1544
+ * application's `hookTimeoutMs` still bounds `onStop`). Default: 4 000 ms.
1545
+ */
1546
+ readonly drainTimeoutMs?: number;
1547
+ /**
1548
+ * CORS: preflights are answered before routing and guards (never by a route), and replies to
1549
+ * allowed origins get `access-control-allow-origin`, identically on every adapter. Default: off.
1550
+ *
1551
+ * ```ts
1552
+ * cors: { origin: ['https://app.example.com', /^https:\/\/[a-z0-9-]+\.preview\.example\.com$/], credentials: true }
1553
+ * ```
1554
+ */
1555
+ readonly cors?: CorsOptions | false;
1556
+ /**
1557
+ * Security headers added to every reply that does not set them (`@Res()` raw replies excepted):
1558
+ * `true` (default) for the API defaults, an object merged over them, `false` for none.
1559
+ */
1560
+ readonly securityHeaders?: boolean | SecurityHeadersOptions;
1561
+ /**
1562
+ * Proxies trusted for `x-forwarded-for` / `x-forwarded-proto` (`ctx.ip`, `ctx.protocol`): a number
1563
+ * of hops, or addresses, CIDR ranges and the aliases `'loopback'`, `'private'`, `'linklocal'`.
1564
+ * Default `false`: the headers are ignored and `ctx.ip` is the adapter's address.
1565
+ */
1566
+ readonly trustProxy?: TrustProxy;
1567
+ /**
1568
+ * What the boot check of route decisions does when a route misses a decision of a required group,
1569
+ * or when no guard of its chain enforces its decision (ZNIGHT023): `'error'` (default) fails the
1570
+ * boot, `'warn'` logs one warning per group (tests that deliberately run without guards).
1571
+ */
1572
+ readonly enforcement?: EnforcementMode;
1573
+ /** Cookies: signing keys and defaults (see {@link HttpCookieOptions}). */
1574
+ readonly cookies?: HttpCookieOptions;
1575
+ /** Server-Sent Events defaults (see {@link HttpSseOptions}). */
1576
+ readonly sse?: HttpSseOptions;
1577
+ }
1578
+ /** The plugin option `cookies`. */
1579
+ interface HttpCookieOptions {
1580
+ /**
1581
+ * HMAC keys of signed cookies (`@SignedCookie`, `{ signed: true }`), at least 32 bytes each: the
1582
+ * first signs, all verify (rotation: deploy `[next, current]`, wait for the longest cookie
1583
+ * lifetime, then `[next]`). Default: none (signed cookies unavailable).
1584
+ */
1585
+ readonly secrets?: string | Uint8Array | readonly (string | Uint8Array)[];
1586
+ /**
1587
+ * Defaults of every cookie set by the application, merged over the secure defaults
1588
+ * `{ path: '/', secure: true, httpOnly: true, sameSite: 'lax' }` (e.g. `{ secure: false }` for
1589
+ * plain `http://` development setups on a non-localhost host).
1590
+ */
1591
+ readonly defaults?: CookieDefaults;
1592
+ }
1593
+ /** The plugin option `sse`. */
1594
+ interface HttpSseOptions {
1595
+ /** Default heartbeat (ms, an integer ≥ 1 000, or `false`). Default: 8 000. */
1596
+ readonly heartbeat?: number | false;
1597
+ /** Default `retry` sent first (ms). Default: none. */
1598
+ readonly retry?: number;
1599
+ /**
1600
+ * Time an event source gets to finish after `ctx.signal` aborted on shutdown or `maxDuration` (ms),
1601
+ * before the stream ends with a jittered `retry`. Must be lower than `drainTimeoutMs`. Default:
1602
+ * 1 000.
1603
+ */
1604
+ readonly shutdownGraceMs?: number;
1605
+ }
1606
+ /** A route registered by the plugin (see {@link HttpPlugin.routes}). */
1607
+ interface HttpRouteSummary {
1608
+ readonly method: string;
1609
+ /** Registered path, prefix included. */
1610
+ readonly path: string;
1611
+ /** Controller display name, or the name of the plugin declaring the route. */
1612
+ readonly controller: string;
1613
+ readonly handler: string;
1614
+ /** `'controller'` for compiled routes, the plugin name for plugin routes. */
1615
+ readonly source: string;
1616
+ /** Effective route metadata (the nearest decision of each group). */
1617
+ readonly meta: Readonly<Record<string, unknown>>;
1618
+ /** Display names of the guards, in chain order (global, controller, route). */
1619
+ readonly guards: readonly string[];
1620
+ /** `'websocket'` for a WebSocket upgrade route (RFC 0003): a gateway of `@znight/websocket`. */
1621
+ readonly upgrade?: "websocket";
1622
+ }
1623
+ /**
1624
+ * The HTTP plugin: serves the controllers compiled into `metadata.extensions.http` with the chosen
1625
+ * adapter.
1626
+ *
1627
+ * ```ts
1628
+ * const app = await Application.create({
1629
+ * metadata: __ZNIGHT_METADATA__,
1630
+ * plugins: [HttpPlugin.configure({ port: 3000, prefix: '/api' })],
1631
+ * })
1632
+ * await app.start()
1633
+ * ```
1634
+ *
1635
+ * Lifecycle: `register` declares the request-scoped externals `HttpContext` and `RequestId`;
1636
+ * `onBootstrap` resolves the options, controllers, guards and interceptors, collects the routes of
1637
+ * other plugins (`httpRoutes()`), checks route decisions and registers the routes; `onStart`
1638
+ * listens; `onStop` stops accepting connections and drains in-flight requests (`drainTimeoutMs`);
1639
+ * `onDispose` closes the server.
1640
+ */
1641
+ declare class HttpPlugin implements Plugin {
1642
+ #private;
1643
+ readonly name = "http";
1644
+ private constructor();
1645
+ /**
1646
+ * Creates the plugin. `options` may be a function of the container, evaluated in `onBootstrap`:
1647
+ * `HttpPlugin.configure(c => ({ port: c.get(AppConfig).port }))`.
1648
+ */
1649
+ static configure(options?: PluginOptions<HttpPluginOptions>): HttpPlugin;
1650
+ /** The adapter (available after `onBootstrap`). */
1651
+ get adapter(): HttpAdapter;
1652
+ /** Address of the server once listening (`String(address)` is its URL), `undefined` otherwise. */
1653
+ get address(): HttpServerAddress | undefined;
1654
+ /**
1655
+ * The path prefix of every route (`''` without prefix, `/api`), for plugins that serve routes or
1656
+ * publish URLs. Readable once the plugin is registered (before or after its `onBootstrap`; at any
1657
+ * time when the options are not a function of the container). Throws the options error when
1658
+ * the options are invalid.
1659
+ */
1660
+ get prefix(): string;
1661
+ /** Registered routes (after `onBootstrap`), in registration order. */
1662
+ get routes(): readonly HttpRouteSummary[];
1663
+ /** Number of requests being processed. */
1664
+ get inflight(): number;
1665
+ /** Number of open event streams (`@Sse`, `HttpResponse.sse()`): health checks, gauges, stream caps. */
1666
+ get streams(): number;
1667
+ /**
1668
+ * The signed wire value of a cookie (`<encoded value>.<signature>`, signed with the first secret of
1669
+ * `cookies.secrets`): what a browser would send back after `setCookie(name, value, { signed: true })`.
1670
+ * For gateways minting cookies and for tests. Available after `onBootstrap`.
1671
+ *
1672
+ * @throws TypeError for an invalid name or value; Error without `cookies.secrets`.
1673
+ */
1674
+ signCookie(name: string, value: string): string;
1675
+ /**
1676
+ * The provider classes the plugin resolves in `onBootstrap` although no compiled metadata
1677
+ * references them: the classes of the `guards`, `interceptors` and `exceptionMappers` options
1678
+ * (instances are left out), in that order, without duplicates. `TestApp` from `@znight/testing`
1679
+ * creates them at boot with their dependencies (an async dependency cannot be created on demand);
1680
+ * pass them to `init: { only }` when restricting the boot with `Application.create()`.
1681
+ *
1682
+ * Options given as a function of the container cannot be read before the bootstrap: the result is
1683
+ * then empty. Never throws: invalid options are reported by `onBootstrap`.
1684
+ */
1685
+ initRoots(): readonly ProviderKey[];
1686
+ /**
1687
+ * Sends a request in memory through the adapter and the whole pipeline (no socket; the server
1688
+ * does not need to listen). Available after `onBootstrap`.
1689
+ *
1690
+ * ```ts
1691
+ * const res = await http.inject({ method: 'POST', url: '/users', body: { name: 'Ada' } })
1692
+ * expect(res.status).toBe(201)
1693
+ * expect(res.json()).toEqual({ id: 1, name: 'Ada' })
1694
+ * ```
1695
+ */
1696
+ inject(request: InjectRequest): Promise<InjectResponse>;
1697
+ /**
1698
+ * Sends a request in memory and resolves at the response headers, the body being read as it is
1699
+ * produced (`body`). Cancelling `body` (or aborting `signal`) disconnects the client. Requires an
1700
+ * adapter implementing `injectStream()` (the four built-in ones do).
1701
+ */
1702
+ injectStream(request: InjectStreamOptions): Promise<InjectStreamResponse>;
1703
+ /**
1704
+ * Opens an event stream in memory (`accept: text/event-stream`) and reads its events like
1705
+ * `EventSource`: `next()`, `take()`, `comments`, `retry`, `lastEventId`, `reconnect()`, `close()`
1706
+ * (the server sees a client disconnect). A response that is not an event stream (404, 204...) is
1707
+ * read to its end (`body`). Resolves at the first chunk of the stream (an `@Sse` stream sends
1708
+ * `retry` or a comment at once), or after 2 000 ms for a stream that sends nothing yet; rejects,
1709
+ * naming the request (which is aborted), when no response headers arrive within 2 000 ms.
1710
+ *
1711
+ * ```ts
1712
+ * await using stream = await http.injectSse({ url: '/orders/o-1/events' })
1713
+ * const event = await stream.next() // rejects after 2 000 ms
1714
+ * expect(event.json()).toMatchObject({ status: 'paid' })
1715
+ * ```
1716
+ */
1717
+ injectSse(request: InjectStreamOptions): Promise<InjectSseResponse>;
1718
+ /**
1719
+ * Runs a WebSocket handshake in memory (RFC 0003, `WebSocketPlugin.connect()`): the same pipeline
1720
+ * as an upgrade request of the adapter (guards, hooks, the route's checks), without socket. The
1721
+ * transport receives `{ kind: 'memory', peer }`. Resolves with the refusal, or `undefined` once
1722
+ * the connection was accepted. Available after `onBootstrap`.
1723
+ */
1724
+ upgradeInMemory(request: InjectRequest, native: MemoryUpgrade): Promise<InjectResponse | undefined>;
1725
+ register(ctx: PluginContext): void;
1726
+ onBootstrap(ctx: PluginContext): void;
1727
+ onStart(ctx: PluginContext): Promise<void>;
1728
+ onStop(ctx: PluginContext): Promise<void>;
1729
+ onDispose(): Promise<void>;
1730
+ }
1731
+ /**
1732
+ * A route served by `HttpPlugin` on behalf of another plugin. It runs through the same pipeline as
1733
+ * controller routes: `http:request` hook, request scope, global guards, then its own `guards`, the
1734
+ * handler, the exception mappers; global interceptors do not wrap it. The handler result is
1735
+ * serialised like a controller result (`HttpResponse`, objects as JSON, strings, streams...).
1736
+ */
1737
+ interface PluginRoute {
1738
+ /** `GET` routes also answer `HEAD`; `ALL` registers every method without its own route. */
1739
+ readonly method: HttpMethod;
1740
+ /** Path relative to the `HttpPlugin` prefix, with the controller grammar (`/openapi.json`, `/docs/_assets/*`). */
1741
+ readonly path: string;
1742
+ /** Handler name, shown in `HttpPlugin.routes`, logs and errors (`document`). */
1743
+ readonly name: string;
1744
+ readonly handler: (ctx: HttpContext) => unknown;
1745
+ /**
1746
+ * Route metadata (`[Public.meta()]`): read by guards (`Public.get(ctx)`) and by the boot check of
1747
+ * required route decorator groups. One entry per group.
1748
+ */
1749
+ readonly meta?: readonly RouteMetaEntry[];
1750
+ /** Guards of the route (provider classes or instances), run after the global guards. */
1751
+ readonly guards?: readonly GuardRef[];
1752
+ /**
1753
+ * Maximum body size of this route (`'100kb'`, `1048576`, `Infinity`), with the grammar of the
1754
+ * `HttpPlugin` option `bodyLimit`, which it replaces for this route only (lower or higher, like
1755
+ * `@BodyLimit` on a controller route). Past it, reading the body answers 413. Default: the
1756
+ * `HttpPlugin` option.
1757
+ */
1758
+ readonly bodyLimit?: ByteSize;
1759
+ }
1760
+ /** What {@link PluginHttpRoutes.httpRoutes} receives. */
1761
+ interface PluginHttpRoutesContext {
1762
+ /** The `HttpPlugin` prefix (`''` or `/api`), already applied to the paths of the routes. */
1763
+ readonly prefix: string;
1764
+ /** The application container (initialised). */
1765
+ readonly container: Container;
1766
+ }
1767
+ /**
1768
+ * Implemented by a plugin that serves HTTP routes. `HttpPlugin` calls `httpRoutes()` once in its
1769
+ * `onBootstrap`, after `container.init()` and whatever the plugin order, then registers the routes
1770
+ * on its adapter. A path that breaks the grammar, or that collides with another route, is ZNIGHT007
1771
+ * naming the plugin.
1772
+ *
1773
+ * ```ts
1774
+ * export class StatusPlugin implements Plugin, PluginHttpRoutes {
1775
+ * readonly name = 'status'
1776
+ * httpRoutes(): readonly PluginRoute[] {
1777
+ * return [{ method: 'GET', path: '/status', name: 'status', handler: () => ({ up: true }), meta: [Public.meta()] }]
1778
+ * }
1779
+ * }
1780
+ * ```
1781
+ */
1782
+ interface PluginHttpRoutes {
1783
+ httpRoutes(context: PluginHttpRoutesContext): readonly PluginRoute[];
1784
+ /**
1785
+ * Called once when `HttpPlugin` starts draining (its `onStop`, whatever the plugin order), after
1786
+ * it stopped accepting connections and started closing its event streams: end the long-lived
1787
+ * responses `HttpPlugin` does not know of (a stream another server produced), so that the drain
1788
+ * does not wait for them until `drainTimeoutMs`. Requests still in flight are served. The drain
1789
+ * waits for a returned promise too (within `drainTimeoutMs`); a failure is logged.
1790
+ */
1791
+ onHttpDrain?(): void | Promise<void>;
1792
+ /**
1793
+ * WebSocket upgrade routes of the plugin (RFC 0003, `@znight/websocket`): see
1794
+ * {@link PluginHttpUpgrades}. Called once in `onBootstrap`, after `httpRoutes()`.
1795
+ */
1796
+ httpUpgrades?(context: PluginHttpUpgradesContext): PluginUpgrades;
1797
+ }
1798
+ /**
1799
+ * The upgrade of a `node:http` server (`'upgrade'` event): every adapter built on `node:http`
1800
+ * (`node`, `express`, `fastify`, `hono` on Node.js), on Node.js and on Bun.
1801
+ */
1802
+ interface NodeUpgrade {
1803
+ readonly kind: "node";
1804
+ /** The `IncomingMessage` of the handshake. */
1805
+ readonly request: unknown;
1806
+ /** The `net.Socket` (a `Duplex`) of the connection. */
1807
+ readonly socket: unknown;
1808
+ /** The first bytes after the handshake headers. */
1809
+ readonly head: Uint8Array;
1810
+ }
1811
+ /** The upgrade of a `Bun.serve()` server (`honoAdapter()` on Bun). */
1812
+ interface BunUpgrade {
1813
+ readonly kind: "bun";
1814
+ /** The Fetch `Request` of the handshake. */
1815
+ readonly request: Request;
1816
+ /** The `Bun.serve()` server (`server.upgrade(request, { data, headers })`). */
1817
+ readonly server: unknown;
1818
+ }
1819
+ /** An in-memory upgrade (`HttpPlugin.upgradeInMemory()`, tests): `peer` belongs to the caller. */
1820
+ interface MemoryUpgrade {
1821
+ readonly kind: "memory";
1822
+ readonly peer: unknown;
1823
+ }
1824
+ /** The native side of an upgrade request, completed by a transport. */
1825
+ type NativeUpgrade = NodeUpgrade | BunUpgrade | MemoryUpgrade;
1826
+ /** What {@link PluginUpgradeRoute.accept} receives to complete the handshake. */
1827
+ interface PendingUpgrade {
1828
+ readonly native: NativeUpgrade;
1829
+ /**
1830
+ * Headers of the `101` response: the headers and cookies the guards set on the context, the
1831
+ * request id and the security headers are not included (they are HTTP response headers); read
1832
+ * once, when the transport completes the handshake.
1833
+ */
1834
+ readonly headers: Readonly<Record<string, HeaderValue>>;
1835
+ /**
1836
+ * The handshake timed out and was answered `408`: the transport must not complete it. Check it
1837
+ * after every `await`, right before completing the handshake.
1838
+ */
1839
+ readonly expired: boolean;
1840
+ /**
1841
+ * Keeps the request scope (and the in-flight count of `HttpPlugin`) open until `closed`
1842
+ * settles: the connection lives in the scope of its handshake. Call it once, after the transport
1843
+ * completed the handshake.
1844
+ */
1845
+ hold(closed: Promise<unknown>): void;
1846
+ }
1847
+ /**
1848
+ * A route answering upgrade requests (`GET` with `upgrade: websocket`), declared by another plugin
1849
+ * through {@link PluginHttpUpgrades.httpUpgrades}.
1850
+ */
1851
+ interface PluginUpgradeRoute {
1852
+ /** Path relative to the `HttpPlugin` prefix, with the route grammar (`/chat`, `/docs/:docId`). */
1853
+ readonly path: string;
1854
+ /** Name of the route (the gateway), shown in `HttpPlugin.routes`, logs and errors. */
1855
+ readonly name: string;
1856
+ /** `ctx.route.controller` during the handshake (the gateway token). Default: the plugin name. */
1857
+ readonly owner?: Token;
1858
+ /** Route metadata (`ctx.route.meta`), one entry per group, checked at boot like plugin routes. */
1859
+ readonly meta?: readonly RouteMetaEntry[];
1860
+ /** Guards of the handshake (classes, instances or provider tokens), after the global guards. */
1861
+ readonly guards?: readonly (GuardRef | Token)[];
1862
+ /** Time the handshake gets before it is answered `408` (ms). Default: none. */
1863
+ readonly timeoutMs?: number;
1864
+ /**
1865
+ * Cheap checks before the guards (origin, subprotocols, capacity), after the protocol checks of
1866
+ * RFC 6455. Throw an `HttpException` to refuse.
1867
+ */
1868
+ precheck?(ctx: HttpContext): void | Promise<void>;
1869
+ /** After the guards: take the connection. Throw an `HttpException` to refuse. */
1870
+ accept(ctx: HttpContext, upgrade: PendingUpgrade): void | Promise<void>;
1871
+ }
1872
+ /** What {@link PluginHttpUpgrades.httpUpgrades} returns. */
1873
+ interface PluginUpgrades {
1874
+ readonly routes: readonly PluginUpgradeRoute[];
1875
+ /**
1876
+ * Server-wide settings of the `websocket` option of `Bun.serve()` (`maxPayloadLength`,
1877
+ * `idleTimeout`, `backpressureLimit`, `sendPings`, `perMessageDeflate`...), used by adapters
1878
+ * serving with `Bun.serve()`. The handlers are set by `HttpPlugin`: they dispatch every event to
1879
+ * the `data` given to `server.upgrade()`, a {@link BunSocketHandlers}.
1880
+ */
1881
+ readonly bunWebSocket?: Readonly<Record<string, unknown>>;
1882
+ }
1883
+ /** What {@link PluginHttpUpgrades.httpUpgrades} receives. */
1884
+ interface PluginHttpUpgradesContext extends PluginHttpRoutesContext {
1885
+ /** Name of the adapter (`node`, `fastify`...). */
1886
+ readonly adapter: string;
1887
+ /**
1888
+ * The native upgrades the adapter produces (`node`: a `node:http` server, `bun`: `Bun.serve()`),
1889
+ * `undefined` when the adapter cannot upgrade connections.
1890
+ */
1891
+ readonly upgrades: "node" | "bun" | undefined;
1892
+ }
1893
+ /**
1894
+ * Implemented, next to {@link PluginHttpRoutes}, by a plugin that accepts WebSocket connections
1895
+ * (`@znight/websocket`). `HttpPlugin` calls `httpUpgrades()` once in its `onBootstrap`, after
1896
+ * `httpRoutes()`, whatever the plugin order, and asks its adapter to route the matching upgrade
1897
+ * requests to the pipeline. `onHttpDrain()` is where the plugin closes its connections.
1898
+ */
1899
+ interface PluginHttpUpgrades {
1900
+ httpUpgrades(context: PluginHttpUpgradesContext): PluginUpgrades;
1901
+ }
1902
+ /**
1903
+ * The `data` a transport gives to `server.upgrade()` on `Bun.serve()`: `HttpPlugin` dispatches
1904
+ * every event of the socket to it.
1905
+ */
1906
+ interface BunSocketHandlers {
1907
+ open(ws: unknown): void;
1908
+ message(ws: unknown, message: string | Uint8Array): void;
1909
+ close(ws: unknown, code: number, reason: string): void;
1910
+ drain(ws: unknown): void;
1911
+ pong?(ws: unknown): void;
1912
+ }
1913
+ /** What `HttpPlugin` gives to {@link AdapterUpgrades.enable}. */
1914
+ interface UpgradeHandlerOptions {
1915
+ /** Whether an upgrade request to the raw `path` (no query string) is for Znight. */
1916
+ match(path: string): boolean;
1917
+ /**
1918
+ * Runs the handshake through the pipeline. Resolves with the refusal to write (an HTTP response),
1919
+ * or `undefined` once the transport took the connection. Never rejects.
1920
+ */
1921
+ handle(request: AdapterRequest, native: NodeUpgrade | BunUpgrade): Promise<AdapterReply | undefined>;
1922
+ /** The `websocket` option of `Bun.serve()` (settings and dispatching handlers), Bun adapters only. */
1923
+ readonly bunWebSocket: Readonly<Record<string, unknown>>;
1924
+ }
1925
+ /** The optional upgrade capability of an adapter (RFC 0003 §8.2.3). */
1926
+ interface AdapterUpgrades {
1927
+ /** The native upgrades the adapter produces. */
1928
+ readonly kind: "node" | "bun";
1929
+ /** Called once, before `listen()`, when the application has upgrade routes. */
1930
+ enable(options: UpgradeHandlerOptions): void;
1931
+ }
1932
+ /** A request, as seen by the plugin. */
1933
+ interface AdapterRequest {
1934
+ /** Upper-case method. */
1935
+ readonly method: string;
1936
+ /** Raw request target: path and query string, percent-encoded (`/users/a%20b?page=2`). */
1937
+ readonly url: string;
1938
+ /** Request headers with lower-case names (repeated headers joined with `, `). */
1939
+ readonly headers: Readonly<Record<string, string | undefined>>;
1940
+ /**
1941
+ * Client address, when known: the socket peer, or the address the server framework derives from
1942
+ * its own proxy settings (Fastify `trustProxy`, Express `trust proxy`). This is `ctx.ip` unless
1943
+ * the plugin option `trustProxy` is set.
1944
+ */
1945
+ readonly ip: string | undefined;
1946
+ /**
1947
+ * Address of the socket peer, **never** taken from headers: where the plugin starts walking
1948
+ * `x-forwarded-for` with `trustProxy`. Optional (`ip` is used without it); the built-in adapters
1949
+ * set it.
1950
+ */
1951
+ readonly remoteAddress?: string | undefined;
1952
+ /** `true` when the connection uses TLS (`ctx.protocol` without a trusted proxy). Optional. */
1953
+ readonly encrypted?: boolean;
1954
+ /** Native request object (`@Req()`). */
1955
+ readonly raw: unknown;
1956
+ /** Native response object (`@Res()`). */
1957
+ readonly rawResponse: unknown;
1958
+ /**
1959
+ * Reads the whole body (once). Rejects with a `PayloadTooLargeException` when it exceeds `limit`
1960
+ * bytes (checked on `content-length` first, then while reading).
1961
+ */
1962
+ readBody(limit: number): Promise<Uint8Array>;
1963
+ /**
1964
+ * The body as a Web stream, read as the consumer pulls it (once; exclusive with `readBody`). It
1965
+ * errors with a `PayloadTooLargeException` past `limit` bytes (`content-length` checked first) and
1966
+ * with a `BadRequestException` when the client aborts. Cancelling it discards the rest of the body
1967
+ * without closing the connection before the response is written. Optional: without it the plugin
1968
+ * streams the result of `readBody` (multipart uploads are then buffered before being parsed).
1969
+ */
1970
+ streamBody?(limit: number): ReadableStream<Uint8Array>;
1971
+ /**
1972
+ * Whether the request has a body, asked only when its headers declare none (neither
1973
+ * `content-length` nor `transfer-encoding`: Web `Request`s built in memory). Return `false` for
1974
+ * requests read from an HTTP/1.1 connection (without those headers they have no body). Optional:
1975
+ * no body is assumed otherwise.
1976
+ */
1977
+ hasBody?(): boolean;
1978
+ /** Status written by a handler that took over the response with `@Res()`, when known. */
1979
+ rawStatus?(): number | undefined;
1980
+ /**
1981
+ * Registers a listener called once when the client disconnects before the response was fully
1982
+ * written. When it already happened, the listener is called on the next microtask. Never called
1983
+ * after a complete response. Optional: without it, `ctx.signal` only aborts when the response
1984
+ * body is cancelled or an event stream is closed by the server.
1985
+ */
1986
+ onAbort?(listener: () => void): void;
1987
+ }
1988
+ /**
1989
+ * Body of a reply: bytes, a stream or nothing. Adapters MUST either read a stream to its end or
1990
+ * cancel it (client disconnected, response not sent...): the request scope and the in-flight count
1991
+ * are released then. A `Blob` is sent with its size; the plugin itself never passes one: it sends
1992
+ * files as a stream reading the `Blob`, given in {@link AdapterReply.blob}.
1993
+ */
1994
+ type ReplyBody = Uint8Array | ReadableStream<Uint8Array> | Blob | null;
1995
+ /** A response computed by the plugin, written by the adapter. */
1996
+ interface AdapterReply {
1997
+ readonly status: number;
1998
+ /** Response headers, lower-case names (validated: every name and value can be sent). */
1999
+ readonly headers: Readonly<Record<string, HeaderValue>>;
2000
+ /** `null` for HEAD requests and bodyless statuses (headers such as `content-length` are kept). */
2001
+ readonly body: ReplyBody;
2002
+ /**
2003
+ * The text `body` holds encoded as UTF-8, for a body serialised from a string (JSON, text).
2004
+ * Adapters writing strings MAY send it instead of `body`, which writes the same bytes: Node.js then
2005
+ * writes the headers and the body at once (bytes take a second write).
2006
+ */
2007
+ readonly text?: string;
2008
+ /**
2009
+ * The `Blob` (file) that `body` streams, for adapters able to send it natively (`sendfile`, exact
2010
+ * length: `new Response(blob)` on `Bun.serve()`). An adapter sending it instead of reading `body`
2011
+ * MUST cancel `body` once the server holds the blob (file opened): that releases the request
2012
+ * scope, which may delete the file. Headers (`content-length`...) are the same either way.
2013
+ */
2014
+ readonly blob?: Blob;
2015
+ /** The handler wrote the response itself through `@Res()`: write nothing. */
2016
+ readonly raw?: boolean;
2017
+ /**
2018
+ * The body is a long-lived event stream (a streamed `text/event-stream` body: `@Sse`,
2019
+ * `HttpResponse.sse()`, a proxied `fetch()` response...). Adapters MUST send the headers without
2020
+ * waiting for the first chunk (a quiet stream may send nothing for a while), MUST NOT apply idle or
2021
+ * inactivity timeouts to the response while it is sent, and MUST write each chunk as soon as it is
2022
+ * read (no buffering, no compression).
2023
+ */
2024
+ readonly eventStream?: boolean;
2025
+ }
2026
+ /** Handles one request. Never rejects: errors are already mapped to a reply. */
2027
+ type AdapterHandler = (request: AdapterRequest) => Promise<AdapterReply>;
2028
+ /**
2029
+ * Handles requests no route matched (`404`/`405`, automatic `OPTIONS`) and errors raised by the
2030
+ * server framework before a route handler ran (`error`, e.g. a malformed URL). Never rejects.
2031
+ */
2032
+ type FallbackHandler = (request: AdapterRequest, error?: unknown) => Promise<AdapterReply>;
2033
+ /** A concrete route to register (the plugin expands `ALL` and adds `HEAD` for `GET` routes). */
2034
+ interface AdapterRoute {
2035
+ readonly method: Exclude<HttpMethod, "ALL">;
2036
+ /** Parsed full path (plugin prefix included). Translate it with the helpers of `@znight/http/path`. */
2037
+ readonly path: RoutePath;
2038
+ readonly handler: AdapterHandler;
2039
+ }
2040
+ /** Address of a listening server. Converts to its URL (`String(address)`). */
2041
+ interface HttpServerAddress {
2042
+ /** Host the server is bound to (`0.0.0.0`, `::`, `127.0.0.1`...). */
2043
+ readonly host: string;
2044
+ readonly port: number;
2045
+ /** `IPv4` or `IPv6`. */
2046
+ readonly family: string;
2047
+ /** Base URL usable by clients (`http://127.0.0.1:3000`; wildcard hosts become loopback addresses). */
2048
+ readonly url: string;
2049
+ toString(): string;
2050
+ }
2051
+ /** A request for {@link HttpAdapter.inject} (in-memory, no network). */
2052
+ interface InjectRequest {
2053
+ readonly method: string;
2054
+ /** Path and optional query string (`/users?page=2`). */
2055
+ readonly url: string;
2056
+ readonly headers?: Readonly<Record<string, string>>;
2057
+ /**
2058
+ * Request body: a string or bytes are sent as is (`text/plain` / `application/octet-stream` when
2059
+ * no `content-type` is given), a `FormData` as `multipart/form-data` (files included), a `Blob` /
2060
+ * `File` with its type, anything else as JSON (`application/json`).
2061
+ */
2062
+ readonly body?: unknown;
2063
+ /** Query parameters appended to `url`. */
2064
+ readonly query?: Readonly<Record<string, string | readonly string[]>>;
2065
+ /**
2066
+ * Request cookies (name → value), sent in a `cookie` header after any `cookie` header of
2067
+ * `headers`. Values are encoded like the values of `serializeCookie`, so a value read from
2068
+ * `response.cookies` can be sent back as is (signed values included).
2069
+ */
2070
+ readonly cookies?: Readonly<Record<string, string>>;
2071
+ /**
2072
+ * Cookies signed by `HttpPlugin` with its first secret (`cookies.secrets`) and merged into
2073
+ * `cookies` before dispatch: the way to send a valid `@SignedCookie`. Adapters never receive this
2074
+ * field.
2075
+ */
2076
+ readonly signedCookies?: Readonly<Record<string, string>>;
2077
+ }
2078
+ /** A request for {@link HttpAdapter.injectStream}: an {@link InjectRequest} that can be aborted. */
2079
+ interface InjectStreamRequest extends InjectRequest {
2080
+ /** Aborting it disconnects the client (like cancelling the response body). */
2081
+ readonly signal?: AbortSignal;
2082
+ }
2083
+ /**
2084
+ * A request for `HttpPlugin.injectStream()` / `injectSse()`: an {@link InjectStreamRequest} whose
2085
+ * method defaults to `GET`.
2086
+ */
2087
+ type InjectStreamOptions = Omit<InjectStreamRequest, "method"> & {
2088
+ readonly method?: string;
2089
+ };
2090
+ /** The response of {@link HttpAdapter.inject}. */
2091
+ interface InjectResponse {
2092
+ readonly status: number;
2093
+ /**
2094
+ * Lower-case header names; repeated headers are joined with `, `, except `set-cookie`: an array
2095
+ * with one entry per cookie (cookie values may contain `, `).
2096
+ */
2097
+ readonly headers: Record<string, string | string[]>;
2098
+ /** Body decoded as UTF-8. */
2099
+ readonly body: string;
2100
+ /** Raw body bytes (binary downloads). */
2101
+ readonly bytes: Uint8Array;
2102
+ /** `JSON.parse(body)`. */
2103
+ json<T = unknown>(): T;
2104
+ /** `set-cookie` parsed, by cookie name (the last one wins). */
2105
+ readonly cookies: Readonly<Record<string, SetCookieInfo>>;
2106
+ }
2107
+ /**
2108
+ * The response of {@link HttpAdapter.injectStream}: available as soon as the headers are sent, its
2109
+ * body read as it is produced.
2110
+ */
2111
+ interface InjectStreamResponse {
2112
+ readonly status: number;
2113
+ /** Like {@link InjectResponse.headers} (`set-cookie` as an array). */
2114
+ readonly headers: Record<string, string | string[]>;
2115
+ readonly cookies: Readonly<Record<string, SetCookieInfo>>;
2116
+ /** The body as it is produced. Cancelling it disconnects the client (the server sees `onAbort` and a cancelled reply body). */
2117
+ readonly body: ReadableStream<Uint8Array>;
2118
+ }
2119
+ /** Options of {@link HttpAdapter.close}. */
2120
+ interface AdapterCloseOptions {
2121
+ /** Destroy open connections instead of waiting for in-flight requests. */
2122
+ readonly force?: boolean;
2123
+ }
2124
+ /**
2125
+ * A server implementation. Create one with `nodeAdapter()` (default), `fastifyAdapter()`,
2126
+ * `expressAdapter()` or `honoAdapter()`. An adapter instance serves one plugin.
2127
+ */
2128
+ interface HttpAdapter {
2129
+ /** `node`, `fastify`, `express`, `hono`... (exposed as `ctx.adapter`). */
2130
+ readonly name: string;
2131
+ /** Registers a route. Called during `onBootstrap`, before `listen`/`inject`. */
2132
+ registerRoute(route: AdapterRoute): void;
2133
+ /** Sets the handler of unmatched requests and framework errors. Called once, before `listen`/`inject`. */
2134
+ setFallbackHandler(handler: FallbackHandler): void;
2135
+ /** Starts listening. `port` 0 picks a free port. */
2136
+ listen(port: number, host: string): Promise<HttpServerAddress>;
2137
+ /**
2138
+ * Stops accepting connections, closes idle ones and resolves once in-flight requests completed and
2139
+ * every connection is closed (`force`: destroys them). Idempotent; also releases what the adapter
2140
+ * created when it never listened.
2141
+ */
2142
+ close(options?: AdapterCloseOptions): Promise<void>;
2143
+ /**
2144
+ * Sends a request in memory (no socket), through the same routing and pipeline, and reads the
2145
+ * whole response. The built-in adapters reject an event stream (`text/event-stream`, which would
2146
+ * never end) with a hint to `injectSse()` / `injectStream()`.
2147
+ */
2148
+ inject(request: InjectRequest): Promise<InjectResponse>;
2149
+ /**
2150
+ * In-memory request whose response body is returned as it is produced (resolves at the headers).
2151
+ * Cancelling `body` (or aborting `signal`) disconnects the client. Optional for third-party
2152
+ * adapters; the four built-in adapters implement it.
2153
+ */
2154
+ injectStream?(request: InjectStreamRequest): Promise<InjectStreamResponse>;
2155
+ /**
2156
+ * WebSocket upgrades (RFC 0003): the adapter routes the upgrade requests `HttpPlugin` asks for
2157
+ * to the pipeline, before its own routing. Optional: an adapter without it cannot serve
2158
+ * `@znight/websocket` gateways. The four built-in adapters implement it.
2159
+ */
2160
+ readonly upgrades?: AdapterUpgrades;
2161
+ }
2162
+ export { BodyLimit as $, InferSchemaOutput as $n, ContentDispositionType as $t, ExceptionMapperRef as A, ConflictException as An, Validate as At, HstsOptions as B, NotFoundException as Bn, HttpInterceptorClass as Bt, PluginHttpUpgradesContext as C, ByteSize as Cn, RouteMetaEntry as Ct, PluginHttpRoutes as D, parseQuery as Dn, UploadedFiles as Dt, UpgradeHandlerOptions as E, QueryRecord as En, UploadedFile as Et, HttpRouteSummary as F, HttpErrorBody as Fn, ExceptionMapperClass as Ft, ParseSseOptions as G, UnauthorizedException as Gn, isHttpResponse as Gt, TrustProxy as H, PayloadTooLargeException as Hn, HttpResponseInit as Ht, HttpSseOptions as I, HttpException as In, ExceptionMapperResult as It, parseSseStream as J, ValidationException as Jn, SseEvent as Jt, SseItem as K, UnprocessableEntityException as Kn, StreamSource as Kt, InterceptorRef as L, HttpExceptionOptions as Ln, GuardClass as Lt, HttpCookieOptions as M, GatewayTimeoutException as Mn, CallHandler as Mt, HttpPlugin as N, GoneException as Nn, CanActivate as Nt, PluginHttpRoutesContext as O, BadGatewayException as On, UseGuard as Ot, HttpPluginOptions as P, HeaderValue as Pn, ExceptionMapper as Pt, Body as Q, InferSchemaInput as Qn, ByteRange as Qt, TrailingSlashPolicy as R, InternalServerErrorException as Rn, GuardEnforcement as Rt, PluginHttpUpgrades as S, uploads as Sn, RouteMetaCarrier as St, PluginUpgrades as T, parseByteSize as Tn, Sse as Tt, InjectSseResponse as U, ServiceUnavailableException as Un, RedirectStatus as Ut, SecurityHeadersOptions as V, NotImplementedException as Vn, HttpResponse as Vt, InjectSseWaitOptions as W, TooManyRequestsException as Wn, StreamResponseOptions as Wt, CorsOrigin as X, isHttpException as Xn, SseSource as Xt, CorsOptions as Y, ValidationIssueDetail as Yn, SseOptions as Yt, All as Z, toIssueDetail as Zn, encodeSseEvent as Zt, BunUpgrade as _, RequestId as _n, serializeClearCookie as _r, Res as _t, AdapterRoute as a, HttpMetadata as an, StandardSchemaV1Props as ar, Get as at, NodeUpgrade as b, UploadedFilesOptions as bn, RouteDecoratorInfo as bt, HttpServerAddress as c, HttpParamSource as cn, StandardSchemaV1Types as cr, Headers as ct, InjectStreamOptions as d, HttpSseDefinition as dn, CookieDefaults as dr, Param as dt, FileBody as en, StandardSchemaV1 as er, Controller as et, InjectStreamRequest as f, HttpUploadDefinition as fn, CookieOptions as fr, Patch as ft, BunSocketHandlers as g, RequestAbortedError as gn, parseSetCookie as gr, Req as gt, AdapterUpgrades as h, RequestAbortKind as hn, parseCookieHeader as hr, Query as ht, AdapterRequest as i, HttpControllerDefinition as in, StandardSchemaV1PathSegment as ir, Delete as it, GuardRef as j, ForbiddenException as jn, createRouteDecorator as jt, PluginRoute as k, BadRequestException as kn, UseInterceptor as kt, InjectRequest as l, HttpRouteDefinition as ln, isStandardSchema as lr, HttpCode as lt, ReplyBody as m, HttpRouteInfo as mn, SetCookieInfo as mr, Put as mt, AdapterHandler as n, contentDisposition as nn, StandardSchemaV1Issue as nr, Cookie as nt, FallbackHandler as o, HttpMethod as on, StandardSchemaV1Result as or, Head as ot, InjectStreamResponse as p, HttpContext as pn, ResponseCookie as pr, Post as pt, SseMessage as q, UnsupportedMediaTypeException as qn, SseBody as qt, AdapterReply as r, parseRange as rn, StandardSchemaV1Options as rr, Ctx as rt, HttpAdapter as s, HttpParamDefinition as sn, StandardSchemaV1SuccessResult as sr, Header as st, AdapterCloseOptions as t, FileResponseOptions as tn, StandardSchemaV1FailureResult as tr, ControllerOptions as tt, InjectResponse as u, HttpRouteGroupDefinition as un, ClearCookieOptions as ur, Options as ut, MemoryUpgrade as v, UploadLimits as vn, serializeCookie as vr, RouteDecorator as vt, PluginUpgradeRoute as w, formatByteSize as wn, SignedCookie as wt, PendingUpgrade as x, Uploads as xn, RouteDecoratorOptions as xt, NativeUpgrade as y, UploadedFileOptions as yn, RouteDecoratorArgs as yt, EnforcementMode as z, MethodNotAllowedException as zn, HttpInterceptor as zt };
2163
+ //# sourceMappingURL=adapter-6qC95ViO.d.mts.map