@znight/http 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +2333 -2
- package/dist/adapter-CGSceVEN.d.mts +876 -0
- package/dist/compiler.d.mts +156 -0
- package/dist/compiler.mjs +2180 -0
- package/dist/compiler.mjs.map +1 -0
- package/dist/express.d.mts +39 -0
- package/dist/express.mjs +135 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.d.mts +33 -0
- package/dist/fastify.mjs +191 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/hono.d.mts +49 -0
- package/dist/hono.mjs +319 -0
- package/dist/hono.mjs.map +1 -0
- package/dist/index.d.mts +1254 -0
- package/dist/index.mjs +5618 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node-BUnkdSsa.mjs +183 -0
- package/dist/node-BUnkdSsa.mjs.map +1 -0
- package/dist/node-io-BY1ZDLRQ.mjs +1566 -0
- package/dist/node-io-BY1ZDLRQ.mjs.map +1 -0
- package/dist/node.d.mts +30 -0
- package/dist/node.mjs +1 -0
- package/dist/path-JI87C4k8.d.mts +109 -0
- package/dist/path.d.mts +2 -0
- package/dist/path.mjs +245 -0
- package/dist/path.mjs.map +1 -0
- package/dist/size-D8rsQcV0.mjs +184 -0
- package/dist/size-D8rsQcV0.mjs.map +1 -0
- package/dist/utils-DCbu3a4w.mjs +27 -0
- package/dist/utils-DCbu3a4w.mjs.map +1 -0
- package/package.json +142 -4
- package/src/adapter.ts +240 -0
- package/src/adapters/express.ts +201 -0
- package/src/adapters/fastify.ts +280 -0
- package/src/adapters/hono.ts +488 -0
- package/src/adapters/node-io.ts +368 -0
- package/src/adapters/node.ts +135 -0
- package/src/adapters/shared.ts +508 -0
- package/src/body.ts +135 -0
- package/src/coerce.ts +195 -0
- package/src/compiler/analyze.ts +1513 -0
- package/src/compiler/ast.ts +90 -0
- package/src/compiler/coercion.ts +267 -0
- package/src/compiler/generate.ts +374 -0
- package/src/compiler/index.ts +76 -0
- package/src/compiler/model.ts +260 -0
- package/src/compiler/route-decorators.ts +405 -0
- package/src/compiler/validate.ts +288 -0
- package/src/context.ts +533 -0
- package/src/cookie-settings.ts +68 -0
- package/src/cookie-signer.ts +106 -0
- package/src/cookies.ts +726 -0
- package/src/cors.ts +349 -0
- package/src/decisions.ts +331 -0
- package/src/decorators.ts +486 -0
- package/src/event-channel.ts +197 -0
- package/src/exceptions.ts +257 -0
- package/src/files.ts +429 -0
- package/src/headers.ts +39 -0
- package/src/index.ts +185 -0
- package/src/inject-sse.ts +309 -0
- package/src/interfaces.ts +101 -0
- package/src/metadata.ts +162 -0
- package/src/mime.ts +157 -0
- package/src/multipart.ts +597 -0
- package/src/option-error.ts +31 -0
- package/src/path.ts +288 -0
- package/src/pipeline.ts +918 -0
- package/src/plugin-routes.ts +188 -0
- package/src/plugin.ts +1432 -0
- package/src/proxy.ts +236 -0
- package/src/query.ts +30 -0
- package/src/response.ts +240 -0
- package/src/router.ts +119 -0
- package/src/security-headers.ts +161 -0
- package/src/serialize.ts +264 -0
- package/src/size.ts +66 -0
- package/src/sse-parse.ts +194 -0
- package/src/sse.ts +650 -0
- package/src/standard-schema.ts +81 -0
- package/src/status.ts +78 -0
- package/src/stream.ts +200 -0
- package/src/tokens.ts +21 -0
- package/src/uploads.ts +176 -0
- package/src/utils.ts +31 -0
- package/src/validate.ts +65 -0
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
import { t as RoutePath } from "./path-JI87C4k8.mjs";
|
|
2
|
+
import { 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
|
+
/**
|
|
407
|
+
* Adapter-neutral view of the current HTTP request, shared by guards, interceptors, exception
|
|
408
|
+
* mappers and handlers (`@Ctx()`), and injectable in request-scoped providers.
|
|
409
|
+
*/
|
|
410
|
+
interface HttpContext {
|
|
411
|
+
readonly requestId: RequestId;
|
|
412
|
+
/** Upper-case method. */
|
|
413
|
+
readonly method: string;
|
|
414
|
+
/** Raw request target (`/users/a%20b?page=2`). */
|
|
415
|
+
readonly url: string;
|
|
416
|
+
/** Raw path, without query string (`/users/a%20b`). */
|
|
417
|
+
readonly path: string;
|
|
418
|
+
/** Decoded route parameters (`*` holds the part matched by a trailing wildcard). */
|
|
419
|
+
readonly params: Readonly<Record<string, string>>;
|
|
420
|
+
/** Decoded query string (repeated keys → arrays), parsed on first access. */
|
|
421
|
+
readonly query: Readonly<QueryRecord>;
|
|
422
|
+
/** Request headers, lower-case names. */
|
|
423
|
+
readonly headers: Readonly<Record<string, string | undefined>>;
|
|
424
|
+
/**
|
|
425
|
+
* Parsed body. Populated before the interceptors for routes declaring `@Body()` or upload
|
|
426
|
+
* parameters (and after {@link parseBody} resolved otherwise); `undefined` until then and for
|
|
427
|
+
* empty bodies. Forms (urlencoded and multipart) are records of their text fields.
|
|
428
|
+
*/
|
|
429
|
+
readonly body: unknown;
|
|
430
|
+
/**
|
|
431
|
+
* Files of a `multipart/form-data` body by field name, in body order (standard `File` objects,
|
|
432
|
+
* held in memory). Populated with {@link body}; empty for other bodies.
|
|
433
|
+
*/
|
|
434
|
+
readonly files: Readonly<Record<string, readonly File[]>>;
|
|
435
|
+
/** The matched route, `undefined` for unmatched requests (404/405). */
|
|
436
|
+
readonly route: HttpRouteInfo | undefined;
|
|
437
|
+
/** Per-request storage for guards, interceptors and handlers (`ctx.state.set('user', user)`). */
|
|
438
|
+
readonly state: Map<string | symbol, unknown>;
|
|
439
|
+
/** Native request of the adapter (`IncomingMessage`, `FastifyRequest`, Express `Request`, Web `Request`). */
|
|
440
|
+
readonly request: unknown;
|
|
441
|
+
/** Native response of the adapter (`ServerResponse`, `FastifyReply`, Express `Response`, Hono `Context`). */
|
|
442
|
+
readonly response: unknown;
|
|
443
|
+
/** Adapter name (`node`, `fastify`, `express`, `hono`). */
|
|
444
|
+
readonly adapter: string;
|
|
445
|
+
/**
|
|
446
|
+
* Client address, when the adapter knows it. With the plugin option `trustProxy`, the address
|
|
447
|
+
* found in `x-forwarded-for` behind the trusted proxies; otherwise the adapter's address (the
|
|
448
|
+
* socket peer, or what Fastify / Express derive from their own proxy settings).
|
|
449
|
+
*/
|
|
450
|
+
readonly ip: string | undefined;
|
|
451
|
+
/**
|
|
452
|
+
* `https` when the connection uses TLS, or, behind a trusted proxy (`trustProxy`), when the
|
|
453
|
+
* leftmost `x-forwarded-proto` says so; `http` otherwise.
|
|
454
|
+
*/
|
|
455
|
+
readonly protocol: "http" | "https";
|
|
456
|
+
/** Headers added to the response, whatever its outcome (errors included). */
|
|
457
|
+
readonly responseHeaders: Readonly<Record<string, HeaderValue>>;
|
|
458
|
+
/** A request header (case-insensitive name). */
|
|
459
|
+
header(name: string): string | undefined;
|
|
460
|
+
/** Sets a response header (case-insensitive name), sent with success and error responses. */
|
|
461
|
+
setHeader(name: string, value: HeaderValue): void;
|
|
462
|
+
/** Appends a response header value (`set-cookie`...). */
|
|
463
|
+
appendHeader(name: string, value: string): void;
|
|
464
|
+
/**
|
|
465
|
+
* Request cookies (`cookie` header), parsed on first access: decoded values, the first occurrence
|
|
466
|
+
* of a name wins. A prototype-less, frozen record.
|
|
467
|
+
*/
|
|
468
|
+
readonly cookies: Readonly<Record<string, string>>;
|
|
469
|
+
/**
|
|
470
|
+
* The verified value of a signed cookie (`@SignedCookie`), `undefined` when it is absent, tampered
|
|
471
|
+
* or signed with a key that is no longer in `cookies.secrets`. Verified once per name.
|
|
472
|
+
*
|
|
473
|
+
* @throws Error when the plugin option `cookies.secrets` is not set.
|
|
474
|
+
*/
|
|
475
|
+
signedCookie(name: string): string | undefined;
|
|
476
|
+
/**
|
|
477
|
+
* Sets a cookie on the response, whatever its outcome (errors, redirects included), with the
|
|
478
|
+
* secure defaults (`Path=/; Secure; HttpOnly; SameSite=Lax`, see the plugin option
|
|
479
|
+
* `cookies.defaults`). A later cookie with the same name, domain and path replaces this one; a
|
|
480
|
+
* cookie of the same key in an `HttpResponse` (`withCookie`) wins over it. Everything is validated
|
|
481
|
+
* now (name, value, attributes, prefixes, size), so the stack trace points to the faulty call.
|
|
482
|
+
*
|
|
483
|
+
* @throws TypeError / RangeError for an invalid cookie; Error once the response headers were sent
|
|
484
|
+
* (a running event stream).
|
|
485
|
+
*/
|
|
486
|
+
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
487
|
+
/**
|
|
488
|
+
* Clears a cookie (`Max-Age=0`, with the same domain and path, and the defaults: a `__Host-`
|
|
489
|
+
* cookie is cleared with valid attributes).
|
|
490
|
+
*
|
|
491
|
+
* @throws like {@link setCookie}.
|
|
492
|
+
*/
|
|
493
|
+
clearCookie(name: string, options?: ClearCookieOptions): void;
|
|
494
|
+
/** Cookies set on this response so far (`setCookie` / `clearCookie`), in order. */
|
|
495
|
+
readonly responseCookies: readonly ResponseCookie[];
|
|
496
|
+
/**
|
|
497
|
+
* Aborted when the client disconnects before the response was written, when the response body is
|
|
498
|
+
* cancelled, or when the server closes an event stream (shutdown, `maxDuration`); never after a
|
|
499
|
+
* normal completion. The reason is a {@link RequestAbortedError}. Pass it to the work the request
|
|
500
|
+
* starts (`fetch(url, { signal: ctx.signal })`, `setTimeout(ms, v, { signal })`). Created on first
|
|
501
|
+
* access.
|
|
502
|
+
*/
|
|
503
|
+
readonly signal: AbortSignal;
|
|
504
|
+
/**
|
|
505
|
+
* Reads and parses the body (once): JSON (`application/json`, `+json`), text (`text/*`), forms
|
|
506
|
+
* (`application/x-www-form-urlencoded` and `multipart/form-data` → record of the text fields,
|
|
507
|
+
* repeated names as arrays; files in {@link files}), raw bytes otherwise; `undefined` when empty.
|
|
508
|
+
* Fails with 400 (malformed), 413 (body or upload limits) or 415 (unsupported encoding, refused
|
|
509
|
+
* file type, non-multipart body on an upload route).
|
|
510
|
+
*/
|
|
511
|
+
parseBody(): Promise<unknown>;
|
|
512
|
+
/** The raw body bytes (once; shared with {@link parseBody}), e.g. to check a webhook signature. */
|
|
513
|
+
rawBody(): Promise<Uint8Array>;
|
|
514
|
+
/**
|
|
515
|
+
* The request body as a Web stream, read as it arrives (nothing is buffered): the escape hatch
|
|
516
|
+
* for large uploads (`PUT /files/:name` piped to disk or object storage). The body limit of the
|
|
517
|
+
* route still applies (`@BodyLimit`, `Infinity` to disable): past it the stream errors with a
|
|
518
|
+
* `PayloadTooLargeException` (413). Exclusive with {@link rawBody} / {@link parseBody}: throws when
|
|
519
|
+
* the body was already read (routes with `@Body()` or upload parameters read it before the
|
|
520
|
+
* handler).
|
|
521
|
+
*/
|
|
522
|
+
bodyStream(): ReadableStream<Uint8Array>;
|
|
523
|
+
}
|
|
524
|
+
/** Route methods. `ALL` registers the route for GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS. */
|
|
525
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "ALL";
|
|
526
|
+
/**
|
|
527
|
+
* Where a handler parameter comes from (`file` / `files`: `@UploadedFile` / `@UploadedFiles`, the
|
|
528
|
+
* `name` being the multipart field; `cookie` / `signedCookie`: `@Cookie` / `@SignedCookie`, the
|
|
529
|
+
* `name` being the cookie name).
|
|
530
|
+
*/
|
|
531
|
+
type HttpParamSource = "param" | "query" | "body" | "headers" | "request" | "response" | "context" | "file" | "files" | "cookie" | "signedCookie";
|
|
532
|
+
/** A handler parameter (informational: extraction, coercion and validation happen in `invoke`). */
|
|
533
|
+
interface HttpParamDefinition {
|
|
534
|
+
/** Position in the handler signature. */
|
|
535
|
+
readonly index: number;
|
|
536
|
+
readonly source: HttpParamSource;
|
|
537
|
+
/** Parameter / query key / header name (lower case) / multipart field. Absent: the whole object (every field). */
|
|
538
|
+
readonly name?: string;
|
|
539
|
+
/** Serialised declared type (`number`, `string[]`, `'asc' | 'desc'`...). */
|
|
540
|
+
readonly type?: string;
|
|
541
|
+
readonly optional?: boolean;
|
|
542
|
+
/** `true` when the value is validated with a schema. */
|
|
543
|
+
readonly schema?: boolean;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Files accepted by an upload parameter (`@UploadedFile` / `@UploadedFiles`). The plugin enforces
|
|
547
|
+
* these rules while the multipart body is read; the presence of required files is checked by
|
|
548
|
+
* `invoke` (the `uploads` helpers).
|
|
549
|
+
*/
|
|
550
|
+
interface HttpUploadDefinition {
|
|
551
|
+
/** Multipart field. Absent: files of any field. */
|
|
552
|
+
readonly field?: string;
|
|
553
|
+
/** `true` for `@UploadedFiles` (several files). */
|
|
554
|
+
readonly multiple?: boolean;
|
|
555
|
+
/** `true` when a file must be sent (informational, checked by `invoke`). */
|
|
556
|
+
readonly required?: boolean;
|
|
557
|
+
/** Maximum size of each file in bytes (`Infinity`: no limit). Default: plugin `uploads.maxFileSize`. */
|
|
558
|
+
readonly maxSize?: number;
|
|
559
|
+
/** Maximum number of files (`@UploadedFiles`). Default: plugin `uploads.maxFiles` (1 for `@UploadedFile`). */
|
|
560
|
+
readonly maxFiles?: number;
|
|
561
|
+
/** Accepted media types (`image/png`, `image/*`), compared with the type declared by the client. */
|
|
562
|
+
readonly mimeTypes?: readonly string[];
|
|
563
|
+
}
|
|
564
|
+
/** Options of an `@Sse` route (`HttpRouteDefinition.sse`). */
|
|
565
|
+
interface HttpSseDefinition {
|
|
566
|
+
/** Heartbeat interval in ms (≥ 1 000); `0` disables it. Absent: the plugin option `sse.heartbeat`. */
|
|
567
|
+
readonly heartbeat?: number;
|
|
568
|
+
/** `retry` sent first (ms). Absent: the plugin option `sse.retry`. */
|
|
569
|
+
readonly retry?: number;
|
|
570
|
+
/** The stream ends gracefully after this many ms (≥ 1 000). */
|
|
571
|
+
readonly maxDuration?: number;
|
|
572
|
+
}
|
|
573
|
+
/** A route: one decorated method of a controller. */
|
|
574
|
+
interface HttpRouteDefinition {
|
|
575
|
+
readonly method: HttpMethod;
|
|
576
|
+
/** Path of the route decorator, normalised (`/:id`). */
|
|
577
|
+
readonly path: string;
|
|
578
|
+
/** `joinPaths(controller.basePath, path)` (without the plugin `prefix`). */
|
|
579
|
+
readonly fullPath: string;
|
|
580
|
+
/** Name of the handler method. */
|
|
581
|
+
readonly handler: string;
|
|
582
|
+
/** `@HttpCode(status)`. */
|
|
583
|
+
readonly status?: number;
|
|
584
|
+
/** `@Header(name, value)` entries (names in lower case). */
|
|
585
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
586
|
+
/**
|
|
587
|
+
* Route metadata (`createRouteDecorator`), merged over the controller metadata at runtime, one
|
|
588
|
+
* decision per group: the route's entry of a group replaces the controller's entries of that group
|
|
589
|
+
* (see {@link HttpMetadata.groups}).
|
|
590
|
+
*/
|
|
591
|
+
readonly meta: Readonly<Record<string, unknown>>;
|
|
592
|
+
/** Route guards (provider tokens), run after global and controller guards. */
|
|
593
|
+
readonly guards: readonly Token[];
|
|
594
|
+
/** Route interceptors (provider tokens), innermost. */
|
|
595
|
+
readonly interceptors: readonly Token[];
|
|
596
|
+
readonly params: readonly HttpParamDefinition[];
|
|
597
|
+
/**
|
|
598
|
+
* `@BodyLimit` (route or controller): maximum body size in bytes (`Infinity`: no limit). Default:
|
|
599
|
+
* the plugin `bodyLimit`, or for upload routes the sum of the file limits plus `bodyLimit`.
|
|
600
|
+
*/
|
|
601
|
+
readonly bodyLimit?: number;
|
|
602
|
+
/**
|
|
603
|
+
* Upload rules (one per `@UploadedFile` / `@UploadedFiles` parameter). Routes with uploads parse
|
|
604
|
+
* `multipart/form-data` bodies before the interceptors and answer 415 to other non-empty bodies.
|
|
605
|
+
*/
|
|
606
|
+
readonly uploads?: readonly HttpUploadDefinition[];
|
|
607
|
+
/** `true` when the handler takes `@Res()` and writes the response itself. */
|
|
608
|
+
readonly rawResponse?: boolean;
|
|
609
|
+
/**
|
|
610
|
+
* `@Sse` route (GET only): the handler result (an `AsyncIterable`, an `Iterable` or a
|
|
611
|
+
* `ReadableStream` of `SseEvent`) is served as an event stream; an `HttpResponse` or a Web
|
|
612
|
+
* `Response` is sent as usual. `{}` without options.
|
|
613
|
+
*/
|
|
614
|
+
readonly sse?: HttpSseDefinition;
|
|
615
|
+
/**
|
|
616
|
+
* Calls the handler: extracts, coerces and validates every argument from the context, then calls
|
|
617
|
+
* the method on the controller instance. May return a promise.
|
|
618
|
+
*/
|
|
619
|
+
readonly invoke: (controller: any, ctx: HttpContext) => unknown;
|
|
620
|
+
}
|
|
621
|
+
/** A controller: a provider whose methods are routes. */
|
|
622
|
+
interface HttpControllerDefinition {
|
|
623
|
+
/** Provider token of the controller class. */
|
|
624
|
+
readonly token: Token;
|
|
625
|
+
/** Normalised base path (`/users`, `/` when absent). */
|
|
626
|
+
readonly basePath: string;
|
|
627
|
+
/** Controller guards (provider tokens). */
|
|
628
|
+
readonly guards: readonly Token[];
|
|
629
|
+
/** Controller interceptors (provider tokens). */
|
|
630
|
+
readonly interceptors: readonly Token[];
|
|
631
|
+
/** Controller metadata (`createRouteDecorator` on the class). */
|
|
632
|
+
readonly meta: Readonly<Record<string, unknown>>;
|
|
633
|
+
readonly routes: readonly HttpRouteDefinition[];
|
|
634
|
+
}
|
|
635
|
+
/** A route decorator group (`createRouteDecorator(key, { group, required })`). */
|
|
636
|
+
interface HttpRouteGroupDefinition {
|
|
637
|
+
/** Keys of the decorators of the group, sorted. */
|
|
638
|
+
readonly keys: readonly string[];
|
|
639
|
+
/** Every route must carry a decision of the group. */
|
|
640
|
+
readonly required: boolean;
|
|
641
|
+
}
|
|
642
|
+
/** `metadata.extensions.http`. */
|
|
643
|
+
interface HttpMetadata {
|
|
644
|
+
readonly controllers: readonly HttpControllerDefinition[];
|
|
645
|
+
/**
|
|
646
|
+
* Route decorator groups that need to be known at runtime (required, holding several keys, or
|
|
647
|
+
* named differently from their key): group → `{ keys, required }`. A key belongs to one group at
|
|
648
|
+
* most; a key listed nowhere is its own group. Absent when no group needs it.
|
|
649
|
+
*/
|
|
650
|
+
readonly groups?: Readonly<Record<string, HttpRouteGroupDefinition>>;
|
|
651
|
+
}
|
|
652
|
+
declare module "@znight/core" {
|
|
653
|
+
interface ZnightExtensionsMetadata {
|
|
654
|
+
readonly http?: HttpMetadata;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/** A request, as seen by the plugin. */
|
|
658
|
+
interface AdapterRequest {
|
|
659
|
+
/** Upper-case method. */
|
|
660
|
+
readonly method: string;
|
|
661
|
+
/** Raw request target: path and query string, percent-encoded (`/users/a%20b?page=2`). */
|
|
662
|
+
readonly url: string;
|
|
663
|
+
/** Request headers with lower-case names (repeated headers joined with `, `). */
|
|
664
|
+
readonly headers: Readonly<Record<string, string | undefined>>;
|
|
665
|
+
/**
|
|
666
|
+
* Client address, when known: the socket peer, or the address the server framework derives from
|
|
667
|
+
* its own proxy settings (Fastify `trustProxy`, Express `trust proxy`). This is `ctx.ip` unless
|
|
668
|
+
* the plugin option `trustProxy` is set.
|
|
669
|
+
*/
|
|
670
|
+
readonly ip: string | undefined;
|
|
671
|
+
/**
|
|
672
|
+
* Address of the socket peer, **never** taken from headers: where the plugin starts walking
|
|
673
|
+
* `x-forwarded-for` with `trustProxy`. Optional (`ip` is used without it); the built-in adapters
|
|
674
|
+
* set it.
|
|
675
|
+
*/
|
|
676
|
+
readonly remoteAddress?: string | undefined;
|
|
677
|
+
/** `true` when the connection uses TLS (`ctx.protocol` without a trusted proxy). Optional. */
|
|
678
|
+
readonly encrypted?: boolean;
|
|
679
|
+
/** Native request object (`@Req()`). */
|
|
680
|
+
readonly raw: unknown;
|
|
681
|
+
/** Native response object (`@Res()`). */
|
|
682
|
+
readonly rawResponse: unknown;
|
|
683
|
+
/**
|
|
684
|
+
* Reads the whole body (once). Rejects with a `PayloadTooLargeException` when it exceeds `limit`
|
|
685
|
+
* bytes (checked on `content-length` first, then while reading).
|
|
686
|
+
*/
|
|
687
|
+
readBody(limit: number): Promise<Uint8Array>;
|
|
688
|
+
/**
|
|
689
|
+
* The body as a Web stream, read as the consumer pulls it (once; exclusive with `readBody`). It
|
|
690
|
+
* errors with a `PayloadTooLargeException` past `limit` bytes (`content-length` checked first) and
|
|
691
|
+
* with a `BadRequestException` when the client aborts. Cancelling it discards the rest of the body
|
|
692
|
+
* without closing the connection before the response is written. Optional: without it the plugin
|
|
693
|
+
* streams the result of `readBody` (multipart uploads are then buffered before being parsed).
|
|
694
|
+
*/
|
|
695
|
+
streamBody?(limit: number): ReadableStream<Uint8Array>;
|
|
696
|
+
/**
|
|
697
|
+
* Whether the request has a body, asked only when its headers declare none (neither
|
|
698
|
+
* `content-length` nor `transfer-encoding`: Web `Request`s built in memory). Return `false` for
|
|
699
|
+
* requests read from an HTTP/1.1 connection (without those headers they have no body). Optional:
|
|
700
|
+
* no body is assumed otherwise.
|
|
701
|
+
*/
|
|
702
|
+
hasBody?(): boolean;
|
|
703
|
+
/** Status written by a handler that took over the response with `@Res()`, when known. */
|
|
704
|
+
rawStatus?(): number | undefined;
|
|
705
|
+
/**
|
|
706
|
+
* Registers a listener called once when the client disconnects before the response was fully
|
|
707
|
+
* written. When it already happened, the listener is called on the next microtask. Never called
|
|
708
|
+
* after a complete response. Optional: without it, `ctx.signal` only aborts when the response
|
|
709
|
+
* body is cancelled or an event stream is closed by the server.
|
|
710
|
+
*/
|
|
711
|
+
onAbort?(listener: () => void): void;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Body of a reply: bytes, a stream or nothing. Adapters MUST either read a stream to its end or
|
|
715
|
+
* cancel it (client disconnected, response not sent...): the request scope and the in-flight count
|
|
716
|
+
* are released then. A `Blob` is sent with its size; the plugin itself never passes one: it sends
|
|
717
|
+
* files as a stream reading the `Blob`, given in {@link AdapterReply.blob}.
|
|
718
|
+
*/
|
|
719
|
+
type ReplyBody = Uint8Array | ReadableStream<Uint8Array> | Blob | null;
|
|
720
|
+
/** A response computed by the plugin, written by the adapter. */
|
|
721
|
+
interface AdapterReply {
|
|
722
|
+
readonly status: number;
|
|
723
|
+
/** Response headers, lower-case names (validated: every name and value can be sent). */
|
|
724
|
+
readonly headers: Readonly<Record<string, HeaderValue>>;
|
|
725
|
+
/** `null` for HEAD requests and bodyless statuses (headers such as `content-length` are kept). */
|
|
726
|
+
readonly body: ReplyBody;
|
|
727
|
+
/**
|
|
728
|
+
* The `Blob` (file) that `body` streams, for adapters able to send it natively (`sendfile`, exact
|
|
729
|
+
* length: `new Response(blob)` on `Bun.serve()`). An adapter sending it instead of reading `body`
|
|
730
|
+
* MUST cancel `body` once the server holds the blob (file opened): that releases the request
|
|
731
|
+
* scope, which may delete the file. Headers (`content-length`...) are the same either way.
|
|
732
|
+
*/
|
|
733
|
+
readonly blob?: Blob;
|
|
734
|
+
/** The handler wrote the response itself through `@Res()`: write nothing. */
|
|
735
|
+
readonly raw?: boolean;
|
|
736
|
+
/**
|
|
737
|
+
* The body is a long-lived event stream (a streamed `text/event-stream` body: `@Sse`,
|
|
738
|
+
* `HttpResponse.sse()`, a proxied `fetch()` response...). Adapters MUST send the headers without
|
|
739
|
+
* waiting for the first chunk (a quiet stream may send nothing for a while), MUST NOT apply idle or
|
|
740
|
+
* inactivity timeouts to the response while it is sent, and MUST write each chunk as soon as it is
|
|
741
|
+
* read (no buffering, no compression).
|
|
742
|
+
*/
|
|
743
|
+
readonly eventStream?: boolean;
|
|
744
|
+
}
|
|
745
|
+
/** Handles one request. Never rejects: errors are already mapped to a reply. */
|
|
746
|
+
type AdapterHandler = (request: AdapterRequest) => Promise<AdapterReply>;
|
|
747
|
+
/**
|
|
748
|
+
* Handles requests no route matched (`404`/`405`, automatic `OPTIONS`) and errors raised by the
|
|
749
|
+
* server framework before a route handler ran (`error`, e.g. a malformed URL). Never rejects.
|
|
750
|
+
*/
|
|
751
|
+
type FallbackHandler = (request: AdapterRequest, error?: unknown) => Promise<AdapterReply>;
|
|
752
|
+
/** A concrete route to register (the plugin expands `ALL` and adds `HEAD` for `GET` routes). */
|
|
753
|
+
interface AdapterRoute {
|
|
754
|
+
readonly method: Exclude<HttpMethod, "ALL">;
|
|
755
|
+
/** Parsed full path (plugin prefix included). Translate it with the helpers of `@znight/http/path`. */
|
|
756
|
+
readonly path: RoutePath;
|
|
757
|
+
readonly handler: AdapterHandler;
|
|
758
|
+
}
|
|
759
|
+
/** Address of a listening server. Converts to its URL (`String(address)`). */
|
|
760
|
+
interface HttpServerAddress {
|
|
761
|
+
/** Host the server is bound to (`0.0.0.0`, `::`, `127.0.0.1`...). */
|
|
762
|
+
readonly host: string;
|
|
763
|
+
readonly port: number;
|
|
764
|
+
/** `IPv4` or `IPv6`. */
|
|
765
|
+
readonly family: string;
|
|
766
|
+
/** Base URL usable by clients (`http://127.0.0.1:3000`; wildcard hosts become loopback addresses). */
|
|
767
|
+
readonly url: string;
|
|
768
|
+
toString(): string;
|
|
769
|
+
}
|
|
770
|
+
/** A request for {@link HttpAdapter.inject} (in-memory, no network). */
|
|
771
|
+
interface InjectRequest {
|
|
772
|
+
readonly method: string;
|
|
773
|
+
/** Path and optional query string (`/users?page=2`). */
|
|
774
|
+
readonly url: string;
|
|
775
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
776
|
+
/**
|
|
777
|
+
* Request body: a string or bytes are sent as is (`text/plain` / `application/octet-stream` when
|
|
778
|
+
* no `content-type` is given), a `FormData` as `multipart/form-data` (files included), a `Blob` /
|
|
779
|
+
* `File` with its type, anything else as JSON (`application/json`).
|
|
780
|
+
*/
|
|
781
|
+
readonly body?: unknown;
|
|
782
|
+
/** Query parameters appended to `url`. */
|
|
783
|
+
readonly query?: Readonly<Record<string, string | readonly string[]>>;
|
|
784
|
+
/**
|
|
785
|
+
* Request cookies (name → value), sent in a `cookie` header after any `cookie` header of
|
|
786
|
+
* `headers`. Values are encoded like the values of `serializeCookie`, so a value read from
|
|
787
|
+
* `response.cookies` can be sent back as is (signed values included).
|
|
788
|
+
*/
|
|
789
|
+
readonly cookies?: Readonly<Record<string, string>>;
|
|
790
|
+
/**
|
|
791
|
+
* Cookies signed by `HttpPlugin` with its first secret (`cookies.secrets`) and merged into
|
|
792
|
+
* `cookies` before dispatch: the way to send a valid `@SignedCookie`. Adapters never receive this
|
|
793
|
+
* field.
|
|
794
|
+
*/
|
|
795
|
+
readonly signedCookies?: Readonly<Record<string, string>>;
|
|
796
|
+
}
|
|
797
|
+
/** A request for {@link HttpAdapter.injectStream}: an {@link InjectRequest} that can be aborted. */
|
|
798
|
+
interface InjectStreamRequest extends InjectRequest {
|
|
799
|
+
/** Aborting it disconnects the client (like cancelling the response body). */
|
|
800
|
+
readonly signal?: AbortSignal;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* A request for `HttpPlugin.injectStream()` / `injectSse()`: an {@link InjectStreamRequest} whose
|
|
804
|
+
* method defaults to `GET`.
|
|
805
|
+
*/
|
|
806
|
+
type InjectStreamOptions = Omit<InjectStreamRequest, "method"> & {
|
|
807
|
+
readonly method?: string;
|
|
808
|
+
};
|
|
809
|
+
/** The response of {@link HttpAdapter.inject}. */
|
|
810
|
+
interface InjectResponse {
|
|
811
|
+
readonly status: number;
|
|
812
|
+
/**
|
|
813
|
+
* Lower-case header names; repeated headers are joined with `, `, except `set-cookie`: an array
|
|
814
|
+
* with one entry per cookie (cookie values may contain `, `).
|
|
815
|
+
*/
|
|
816
|
+
readonly headers: Record<string, string | string[]>;
|
|
817
|
+
/** Body decoded as UTF-8. */
|
|
818
|
+
readonly body: string;
|
|
819
|
+
/** Raw body bytes (binary downloads). */
|
|
820
|
+
readonly bytes: Uint8Array;
|
|
821
|
+
/** `JSON.parse(body)`. */
|
|
822
|
+
json<T = unknown>(): T;
|
|
823
|
+
/** `set-cookie` parsed, by cookie name (the last one wins). */
|
|
824
|
+
readonly cookies: Readonly<Record<string, SetCookieInfo>>;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* The response of {@link HttpAdapter.injectStream}: available as soon as the headers are sent, its
|
|
828
|
+
* body read as it is produced.
|
|
829
|
+
*/
|
|
830
|
+
interface InjectStreamResponse {
|
|
831
|
+
readonly status: number;
|
|
832
|
+
/** Like {@link InjectResponse.headers} (`set-cookie` as an array). */
|
|
833
|
+
readonly headers: Record<string, string | string[]>;
|
|
834
|
+
readonly cookies: Readonly<Record<string, SetCookieInfo>>;
|
|
835
|
+
/** The body as it is produced. Cancelling it disconnects the client (the server sees `onAbort` and a cancelled reply body). */
|
|
836
|
+
readonly body: ReadableStream<Uint8Array>;
|
|
837
|
+
}
|
|
838
|
+
/** Options of {@link HttpAdapter.close}. */
|
|
839
|
+
interface AdapterCloseOptions {
|
|
840
|
+
/** Destroy open connections instead of waiting for in-flight requests. */
|
|
841
|
+
readonly force?: boolean;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* A server implementation. Create one with `nodeAdapter()` (default), `fastifyAdapter()`,
|
|
845
|
+
* `expressAdapter()` or `honoAdapter()`. An adapter instance serves one plugin.
|
|
846
|
+
*/
|
|
847
|
+
interface HttpAdapter {
|
|
848
|
+
/** `node`, `fastify`, `express`, `hono`... (exposed as `ctx.adapter`). */
|
|
849
|
+
readonly name: string;
|
|
850
|
+
/** Registers a route. Called during `onBootstrap`, before `listen`/`inject`. */
|
|
851
|
+
registerRoute(route: AdapterRoute): void;
|
|
852
|
+
/** Sets the handler of unmatched requests and framework errors. Called once, before `listen`/`inject`. */
|
|
853
|
+
setFallbackHandler(handler: FallbackHandler): void;
|
|
854
|
+
/** Starts listening. `port` 0 picks a free port. */
|
|
855
|
+
listen(port: number, host: string): Promise<HttpServerAddress>;
|
|
856
|
+
/**
|
|
857
|
+
* Stops accepting connections, closes idle ones and resolves once in-flight requests completed and
|
|
858
|
+
* every connection is closed (`force`: destroys them). Idempotent; also releases what the adapter
|
|
859
|
+
* created when it never listened.
|
|
860
|
+
*/
|
|
861
|
+
close(options?: AdapterCloseOptions): Promise<void>;
|
|
862
|
+
/**
|
|
863
|
+
* Sends a request in memory (no socket), through the same routing and pipeline, and reads the
|
|
864
|
+
* whole response. The built-in adapters reject an event stream (`text/event-stream`, which would
|
|
865
|
+
* never end) with a hint to `injectSse()` / `injectStream()`.
|
|
866
|
+
*/
|
|
867
|
+
inject(request: InjectRequest): Promise<InjectResponse>;
|
|
868
|
+
/**
|
|
869
|
+
* In-memory request whose response body is returned as it is produced (resolves at the headers).
|
|
870
|
+
* Cancelling `body` (or aborting `signal`) disconnects the client. Optional for third-party
|
|
871
|
+
* adapters; the four built-in adapters implement it.
|
|
872
|
+
*/
|
|
873
|
+
injectStream?(request: InjectStreamRequest): Promise<InjectStreamResponse>;
|
|
874
|
+
}
|
|
875
|
+
export { PayloadTooLargeException as $, UploadedFileOptions as A, BadRequestException as B, HttpUploadDefinition as C, CookieOptions as Ct, RequestAbortedError as D, parseSetCookie as Dt, RequestAbortKind as E, parseCookieHeader as Et, formatByteSize as F, HeaderValue as G, ForbiddenException as H, parseByteSize as I, HttpExceptionOptions as J, HttpErrorBody as K, QueryRecord as L, Uploads as M, uploads as N, RequestId as O, serializeClearCookie as Ot, ByteSize as P, NotImplementedException as Q, parseQuery as R, HttpSseDefinition as S, CookieDefaults as St, HttpRouteInfo as T, SetCookieInfo as Tt, GatewayTimeoutException as U, ConflictException as V, GoneException as W, MethodNotAllowedException as X, InternalServerErrorException as Y, NotFoundException as Z, HttpMethod as _, StandardSchemaV1Result as _t, AdapterRoute as a, ValidationException as at, HttpRouteDefinition as b, isStandardSchema as bt, HttpServerAddress as c, toIssueDetail as ct, InjectStreamOptions as d, StandardSchemaV1 as dt, ServiceUnavailableException as et, InjectStreamRequest as f, StandardSchemaV1FailureResult as ft, HttpMetadata as g, StandardSchemaV1Props as gt, HttpControllerDefinition as h, StandardSchemaV1PathSegment as ht, AdapterRequest as i, UnsupportedMediaTypeException as it, UploadedFilesOptions as j, UploadLimits as k, serializeCookie as kt, InjectRequest as l, InferSchemaInput as lt, ReplyBody as m, StandardSchemaV1Options as mt, AdapterHandler as n, UnauthorizedException as nt, FallbackHandler as o, ValidationIssueDetail as ot, InjectStreamResponse as p, StandardSchemaV1Issue as pt, HttpException as q, AdapterReply as r, UnprocessableEntityException as rt, HttpAdapter as s, isHttpException as st, AdapterCloseOptions as t, TooManyRequestsException as tt, InjectResponse as u, InferSchemaOutput as ut, HttpParamDefinition as v, StandardSchemaV1SuccessResult as vt, HttpContext as w, ResponseCookie as wt, HttpRouteGroupDefinition as x, ClearCookieOptions as xt, HttpParamSource as y, StandardSchemaV1Types as yt, BadGatewayException as z };
|
|
876
|
+
//# sourceMappingURL=adapter-CGSceVEN.d.mts.map
|