expediate 1.0.4 → 1.0.6
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/CHANGELOG.md +138 -0
- package/CONTRIBUTING.md +150 -0
- package/LICENSE +16 -16
- package/README.md +330 -444
- package/dist/apis.d.ts +504 -27
- package/dist/apis.d.ts.map +1 -1
- package/dist/apis.js +618 -107
- package/dist/apis.js.map +1 -1
- package/dist/cjs/index.js +4066 -0
- package/dist/cjs/package.json +1 -0
- package/dist/git.d.ts +72 -9
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +129 -74
- package/dist/git.js.map +1 -1
- package/dist/http-objects.d.ts +26 -0
- package/dist/http-objects.d.ts.map +1 -0
- package/dist/http-objects.js +588 -0
- package/dist/http-objects.js.map +1 -0
- package/dist/index.d.ts +18 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -24
- package/dist/index.js.map +1 -1
- package/dist/jwt-auth.d.ts +158 -57
- package/dist/jwt-auth.d.ts.map +1 -1
- package/dist/jwt-auth.js +447 -207
- package/dist/jwt-auth.js.map +1 -1
- package/dist/middleware.d.ts +476 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +647 -0
- package/dist/middleware.js.map +1 -0
- package/dist/mimetypes.json +882 -1
- package/dist/misc.d.ts +268 -25
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +449 -168
- package/dist/misc.js.map +1 -1
- package/dist/openapi.d.ts +433 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +624 -0
- package/dist/openapi.js.map +1 -0
- package/dist/router-types.d.ts +760 -0
- package/dist/router-types.d.ts.map +1 -0
- package/dist/router-types.js +23 -0
- package/dist/router-types.js.map +1 -0
- package/dist/router.d.ts +37 -201
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +502 -244
- package/dist/router.js.map +1 -1
- package/dist/static.d.ts +3 -3
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +164 -105
- package/dist/static.js.map +1 -1
- package/docs/THREAT_MODEL.md +52 -0
- package/docs/api-builder-v2-design.md +644 -0
- package/docs/api-builder-v3-design.md +397 -0
- package/docs/api-builder.md +454 -0
- package/docs/benchmark.md +27 -0
- package/docs/body-parsing.md +223 -0
- package/docs/errors.md +359 -0
- package/docs/expediate.png +0 -0
- package/docs/git.md +139 -0
- package/docs/jwt-auth.md +251 -0
- package/docs/logo.svg +12 -0
- package/docs/middleware.md +264 -0
- package/docs/openapi.md +180 -0
- package/docs/router.md +356 -0
- package/docs/static.md +128 -0
- package/docs/wiki.json +123 -0
- package/package.json +47 -8
- package/.npmignore +0 -16
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
import type * as http from 'http';
|
|
2
|
+
import type * as https from 'https';
|
|
3
|
+
import type * as http2 from 'http2';
|
|
4
|
+
import type { BodyOptions, FormPart } from './misc.js';
|
|
5
|
+
/** A key-value map of arbitrary string values. */
|
|
6
|
+
export type StringMap = Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Options accepted by `createRouter()`.
|
|
9
|
+
*/
|
|
10
|
+
export interface RouterOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Secret string used to sign and verify signed cookies.
|
|
13
|
+
*
|
|
14
|
+
* Required when any route calls `res.cookie(name, val, { signed: true })`.
|
|
15
|
+
* When absent, attempting to set a signed cookie throws at runtime.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const app = createRouter({ secret: process.env.COOKIE_SECRET });
|
|
20
|
+
* app.get('/set', (_req, res) =>
|
|
21
|
+
* res.cookie('session', 'user-id', { signed: true }).send('ok'));
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
secret?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Global request timeout in milliseconds.
|
|
27
|
+
*
|
|
28
|
+
* When a request handler does not start writing a response within this
|
|
29
|
+
* period, the socket is marked idle and a **408 Request Timeout** response
|
|
30
|
+
* is sent automatically. The timeout is reset on every response write.
|
|
31
|
+
*
|
|
32
|
+
* Set to `0` or omit to disable the timeout entirely.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const app = createRouter({ timeout: 30_000 }); // 30 s
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
timeout?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Trust the `X-Forwarded-For` header when resolving `req.ip`.
|
|
42
|
+
*
|
|
43
|
+
* When `true`, `req.ip` is set to the **first** (leftmost) value in the
|
|
44
|
+
* `X-Forwarded-For` header, which is the IP address reported by the
|
|
45
|
+
* outermost client. This is the correct setting when the server sits behind
|
|
46
|
+
* a reverse proxy (e.g. nginx, AWS ALB) that injects this header.
|
|
47
|
+
*
|
|
48
|
+
* When `false` (default), `req.ip` contains the raw socket remote address
|
|
49
|
+
* and the `X-Forwarded-For` header is ignored. Use this mode when the
|
|
50
|
+
* server is directly internet-facing to prevent IP spoofing.
|
|
51
|
+
*
|
|
52
|
+
* @default false
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* // Behind a trusted reverse proxy
|
|
57
|
+
* const app = createRouter({ trustProxy: true });
|
|
58
|
+
* app.get('/me', (req, res) => res.send(req.ip));
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
trustProxy?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extended HTTP incoming message that carries parsed routing metadata.
|
|
65
|
+
* Augments the standard `http.IncomingMessage` with fields populated by
|
|
66
|
+
* the router before any middleware is invoked.
|
|
67
|
+
*/
|
|
68
|
+
export interface RouterRequest extends http.IncomingMessage {
|
|
69
|
+
/** The original, unmodified URL string from the HTTP request. */
|
|
70
|
+
originalUrl: string;
|
|
71
|
+
/**
|
|
72
|
+
* The current path being matched. Prefix-style layers (`use`)
|
|
73
|
+
* rewrite this field after matching so that nested routers only see the
|
|
74
|
+
* remaining suffix. Exact-method layers (`all`, `get`, `post`, etc.) leave it
|
|
75
|
+
* untouched so that chained middlewares sharing the same path each match.
|
|
76
|
+
*/
|
|
77
|
+
path: string;
|
|
78
|
+
/**
|
|
79
|
+
* Aggregated parameters from all sources.
|
|
80
|
+
* URL query parameters are merged first; named route parameters (from both
|
|
81
|
+
* plain-string and RegExp patterns) override them when a route matches.
|
|
82
|
+
*/
|
|
83
|
+
params: StringMap;
|
|
84
|
+
/**
|
|
85
|
+
* Structured query buckets:
|
|
86
|
+
* - `url` — parameters parsed from the query string. Repeated keys
|
|
87
|
+
* (e.g. `?tag=a&tag=b`) produce an array value.
|
|
88
|
+
* - `route` — named parameters captured from the route pattern.
|
|
89
|
+
*/
|
|
90
|
+
queries: {
|
|
91
|
+
url?: Record<string, string | string[]>;
|
|
92
|
+
route?: StringMap;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Parsed cookies sent with the request.
|
|
96
|
+
*
|
|
97
|
+
* Values are decoded automatically:
|
|
98
|
+
* - `j:` prefixed values are JSON-parsed and returned as their JS type.
|
|
99
|
+
* - `s:` prefixed values are HMAC-verified using the router secret; if
|
|
100
|
+
* valid, the inner value (decoded in turn) is returned. Cookies that
|
|
101
|
+
* fail verification are **not** included in this map.
|
|
102
|
+
* - Plain string values are returned unchanged.
|
|
103
|
+
*/
|
|
104
|
+
cookies: Record<string, unknown>;
|
|
105
|
+
/**
|
|
106
|
+
* The parsed request body, or `undefined` until something populates it.
|
|
107
|
+
*
|
|
108
|
+
* It is set by a body-parsing middleware run ahead of the handler
|
|
109
|
+
* (`json()`, `formData()`, `formEncoded()`, `parseBody()`, …) or, on first
|
|
110
|
+
* call, cached by the promise-based readers {@link RouterRequest.json},
|
|
111
|
+
* {@link RouterRequest.text}, and {@link RouterRequest.formData}.
|
|
112
|
+
*
|
|
113
|
+
* Typed as `unknown` because the shape depends on the parser used (a parsed
|
|
114
|
+
* JS value for JSON, a `string` for text, `FormPart[]` for multipart).
|
|
115
|
+
* Narrow it before use, e.g. `const { name } = req.body as { name: string }`.
|
|
116
|
+
*/
|
|
117
|
+
body?: unknown;
|
|
118
|
+
/**
|
|
119
|
+
* Alias for `req.queries.url` — the parsed URL query-string parameters.
|
|
120
|
+
* Mirrors the Express `req.query` property.
|
|
121
|
+
*/
|
|
122
|
+
query: Record<string, string | string[]>;
|
|
123
|
+
/**
|
|
124
|
+
* The hostname derived from the `Host` (or `X-Forwarded-Host` when
|
|
125
|
+
* `trustProxy` is enabled) header, with any port suffix stripped.
|
|
126
|
+
*/
|
|
127
|
+
hostname: string;
|
|
128
|
+
/**
|
|
129
|
+
* The request protocol: `'https'` when the connection is encrypted (TLS or
|
|
130
|
+
* `X-Forwarded-Proto: https` with `trustProxy` enabled), `'http'` otherwise.
|
|
131
|
+
*/
|
|
132
|
+
protocol: string;
|
|
133
|
+
/**
|
|
134
|
+
* `true` when `req.protocol === 'https'`.
|
|
135
|
+
*/
|
|
136
|
+
secure: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Array of IP addresses from the `X-Forwarded-For` header (when
|
|
139
|
+
* `trustProxy` is enabled), ordered from the originating client to the
|
|
140
|
+
* nearest proxy. Empty array when the header is absent or `trustProxy` is
|
|
141
|
+
* disabled.
|
|
142
|
+
*/
|
|
143
|
+
ips: string[];
|
|
144
|
+
/**
|
|
145
|
+
* The URL prefix matched by the nearest `use()` mount point. Mirrors the
|
|
146
|
+
* Express `req.baseUrl` property. Starts as `''` and accumulates each
|
|
147
|
+
* stripped prefix as the request traverses nested `use()` routers.
|
|
148
|
+
*/
|
|
149
|
+
baseUrl: string;
|
|
150
|
+
/**
|
|
151
|
+
* Read and parse the request body as JSON.
|
|
152
|
+
*
|
|
153
|
+
* Returns the parsed value, or `null` when the request has no body.
|
|
154
|
+
* Rejects with `{ status, message }` on parse or transport errors.
|
|
155
|
+
*/
|
|
156
|
+
json(opts?: BodyOptions): Promise<unknown>;
|
|
157
|
+
/**
|
|
158
|
+
* The IP address of the remote client.
|
|
159
|
+
*
|
|
160
|
+
* - When the router is created with `{ trustProxy: true }`, this is the
|
|
161
|
+
* **first** value from the `X-Forwarded-For` header (the originating
|
|
162
|
+
* client address as reported by the proxy chain).
|
|
163
|
+
* - Otherwise this is the raw TCP socket remote address
|
|
164
|
+
* (`req.socket?.remoteAddress`), which cannot be spoofed by the client.
|
|
165
|
+
*
|
|
166
|
+
* Always an empty string when neither source is available.
|
|
167
|
+
*/
|
|
168
|
+
ip: string;
|
|
169
|
+
/**
|
|
170
|
+
* Read and decode the request body as plain text.
|
|
171
|
+
*
|
|
172
|
+
* Returns the body string (decoded using the charset in `Content-Type`,
|
|
173
|
+
* defaulting to UTF-8), or `null` when the request has no body.
|
|
174
|
+
* Rejects with `{ status, message }` on transport errors.
|
|
175
|
+
*/
|
|
176
|
+
text(opts?: BodyOptions): Promise<string | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Read and parse the request body as `multipart/form-data`.
|
|
179
|
+
*
|
|
180
|
+
* Returns an array of {@link FormPart} objects, or `null` when the request
|
|
181
|
+
* has no body. Rejects with `{ status, message }` on parse or transport
|
|
182
|
+
* errors.
|
|
183
|
+
*/
|
|
184
|
+
formData(opts?: BodyOptions): Promise<FormPart[] | null>;
|
|
185
|
+
/**
|
|
186
|
+
* Return the value of a request header by name (case-insensitive).
|
|
187
|
+
*
|
|
188
|
+
* A thin convenience over `req.headers`: the lookup is lowercased, and the
|
|
189
|
+
* `Referer`/`Referrer` spelling variants are treated as equivalent. Returns
|
|
190
|
+
* `undefined` when the header is absent.
|
|
191
|
+
*
|
|
192
|
+
* @param name - The header name (any case).
|
|
193
|
+
* @returns The header value (`string`, or `string[]` for repeated headers
|
|
194
|
+
* like `Set-Cookie`), or `undefined`.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* const ua = req.header('User-Agent');
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
header(name: string): string | string[] | undefined;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Extended HTTP server response with convenience helpers.
|
|
205
|
+
* Augments the standard `http.ServerResponse`.
|
|
206
|
+
*/
|
|
207
|
+
export interface RouterResponse extends http.ServerResponse {
|
|
208
|
+
/**
|
|
209
|
+
* Write `data` (if provided) and end the response.
|
|
210
|
+
* Equivalent to `res.write(data); res.end()`.
|
|
211
|
+
*/
|
|
212
|
+
send(data?: string): void;
|
|
213
|
+
/** Serialise `data` as JSON, set `Content-Type: application/json`, and end. */
|
|
214
|
+
json(data: unknown): void;
|
|
215
|
+
/**
|
|
216
|
+
* Set the HTTP status code and optional response headers, then return
|
|
217
|
+
* `this` so calls can be chained (e.g. `res.status(404).end(...)`).
|
|
218
|
+
*/
|
|
219
|
+
status(code: number, headers?: StringMap): this;
|
|
220
|
+
/** Redirect the client to `url` with a 302 Found response. */
|
|
221
|
+
redirect(url: string): void;
|
|
222
|
+
/**
|
|
223
|
+
* Append a `Set-Cookie` header for the given `name`/`value` pair.
|
|
224
|
+
* Returns `this` to allow chaining.
|
|
225
|
+
*/
|
|
226
|
+
cookie(name: string, value: string | object, options?: CookieOptions): this;
|
|
227
|
+
/**
|
|
228
|
+
* Trigger a file download in the client's browser.
|
|
229
|
+
*
|
|
230
|
+
* Sets the `Content-Disposition: attachment` header (which prompts a
|
|
231
|
+
* "Save As" dialog in browsers), then streams the file at `filepath` using
|
|
232
|
+
* {@link sendFile}.
|
|
233
|
+
*
|
|
234
|
+
* @param filepath - Absolute or relative filesystem path to the file to send.
|
|
235
|
+
* @param filename - Override the file name advertised to the browser.
|
|
236
|
+
* Defaults to `path.basename(filepath)`.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* app.get('/invoice', (_req, res) =>
|
|
241
|
+
* res.download('/var/reports/2024-Q1.pdf', 'invoice-2024-Q1.pdf'));
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
download(filepath: string, filename?: string): void;
|
|
245
|
+
/**
|
|
246
|
+
* Set the `Content-Type` response header and return `this` for chaining.
|
|
247
|
+
*
|
|
248
|
+
* The value is set verbatim — include the charset when needed
|
|
249
|
+
* (e.g. `'text/html; charset=utf-8'`).
|
|
250
|
+
*
|
|
251
|
+
* @param mime - The MIME type string to set.
|
|
252
|
+
* @returns `this` for chaining.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* res.type('text/csv').send(csvData);
|
|
257
|
+
* res.type('application/octet-stream').send(binaryData);
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
type(mime: string): this;
|
|
261
|
+
/**
|
|
262
|
+
* Set the `ETag` response header.
|
|
263
|
+
*
|
|
264
|
+
* By default a **weak** ETag is produced (`W/"value"`), which indicates that
|
|
265
|
+
* the two representations are semantically equivalent but not byte-for-byte
|
|
266
|
+
* identical. Pass `true` as the second argument for a **strong** ETag
|
|
267
|
+
* (`"value"`), which implies byte-level equivalence and is required when
|
|
268
|
+
* byte-range requests must be validated.
|
|
269
|
+
*
|
|
270
|
+
* Returns `this` so calls can be chained before `res.send()` or `res.json()`.
|
|
271
|
+
*
|
|
272
|
+
* @param value - The opaque ETag value (without quotes or `W/` prefix).
|
|
273
|
+
* @param strong - When `true`, produce a strong ETag. Defaults to `false`.
|
|
274
|
+
* @returns `this` for chaining.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* // Weak ETag (default) — most appropriate for dynamic responses
|
|
279
|
+
* res.etag(user.updatedAt.toISOString()).json(user);
|
|
280
|
+
*
|
|
281
|
+
* // Strong ETag — use when the response body is content-addressed
|
|
282
|
+
* res.etag(sha256hex, true).send(fileContent);
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
etag(value: string, strong?: boolean): this;
|
|
286
|
+
/**
|
|
287
|
+
* Set a response header to `value`, replacing any existing value.
|
|
288
|
+
* A chainable wrapper over the native `res.setHeader()`, consistent with the
|
|
289
|
+
* other `res.*` helpers (and matching Fastify's `reply.header()`).
|
|
290
|
+
* Returns `this` for chaining.
|
|
291
|
+
*
|
|
292
|
+
* @param field - The header name.
|
|
293
|
+
* @param value - The header value (`string`, or `string[]` for multi-value
|
|
294
|
+
* headers like `Set-Cookie`).
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* res.header('Cache-Control', 'no-store').json(data);
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
header(field: string, value: string | number | string[]): this;
|
|
302
|
+
/**
|
|
303
|
+
* Append a value to a response header, creating it if it does not exist.
|
|
304
|
+
* When the header already exists, the new value is appended (comma-joined
|
|
305
|
+
* for most headers; `Set-Cookie` accumulates as an array).
|
|
306
|
+
* Returns `this` for chaining.
|
|
307
|
+
*/
|
|
308
|
+
append(field: string, value: string | string[]): this;
|
|
309
|
+
/**
|
|
310
|
+
* Add the given field(s) to the `Vary` response header.
|
|
311
|
+
* Existing values are preserved; duplicates are skipped.
|
|
312
|
+
* Returns `this` for chaining.
|
|
313
|
+
*/
|
|
314
|
+
vary(field: string | string[]): this;
|
|
315
|
+
/**
|
|
316
|
+
* Set the `Location` response header.
|
|
317
|
+
* Returns `this` for chaining.
|
|
318
|
+
*/
|
|
319
|
+
location(url: string): this;
|
|
320
|
+
/**
|
|
321
|
+
* Clear a cookie by name by setting its `Max-Age` to `0` and `Expires` to
|
|
322
|
+
* the Unix epoch. Any `path` or `domain` options passed must match those
|
|
323
|
+
* used when the cookie was originally set.
|
|
324
|
+
* Returns `this` for chaining.
|
|
325
|
+
*/
|
|
326
|
+
clearCookie(name: string, options?: CookieOptions): this;
|
|
327
|
+
/**
|
|
328
|
+
* Send a response whose body is the standard HTTP status message for `code`.
|
|
329
|
+
* Sets the status code, `Content-Type: text/plain`, and ends the response.
|
|
330
|
+
*/
|
|
331
|
+
sendStatus(code: number): void;
|
|
332
|
+
/**
|
|
333
|
+
* Set `Content-Disposition: attachment` with an optional filename.
|
|
334
|
+
* When `filename` is provided, the `Content-Type` header is also set based
|
|
335
|
+
* on the file extension.
|
|
336
|
+
* Returns `this` for chaining.
|
|
337
|
+
*/
|
|
338
|
+
attachment(filename?: string): this;
|
|
339
|
+
/**
|
|
340
|
+
* A plain object for storing response-scoped data shared between middleware
|
|
341
|
+
* and route handlers within a single request/response cycle.
|
|
342
|
+
* Mirrors the Express `res.locals` property.
|
|
343
|
+
*/
|
|
344
|
+
locals: Record<string, unknown>;
|
|
345
|
+
}
|
|
346
|
+
/** Options accepted by `res.cookie()`. */
|
|
347
|
+
export interface CookieOptions {
|
|
348
|
+
/**
|
|
349
|
+
* Sign the cookie value with HMAC-SHA256 using the router's `secret`.
|
|
350
|
+
* Requires `secret` to be passed to `createRouter()`.
|
|
351
|
+
*/
|
|
352
|
+
signed?: boolean;
|
|
353
|
+
/** Expiry date for the cookie. */
|
|
354
|
+
expires?: Date;
|
|
355
|
+
/** Max age in milliseconds; converted to seconds in the `Set-Cookie` header. */
|
|
356
|
+
maxAge?: number;
|
|
357
|
+
/** Cookie path (defaults to `'/'`). */
|
|
358
|
+
path?: string;
|
|
359
|
+
/** Marks the cookie as `HttpOnly` (not accessible via `document.cookie`). */
|
|
360
|
+
httpOnly?: boolean;
|
|
361
|
+
/** Marks the cookie as `Secure` (only sent over HTTPS). */
|
|
362
|
+
secure?: boolean;
|
|
363
|
+
/** `SameSite` attribute value (`'Strict'`, `'Lax'`, or `'None'`). */
|
|
364
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Options for HTTPS / HTTP/2 servers passed to `router.listen()`.
|
|
368
|
+
*
|
|
369
|
+
* When both `key` and `cert` are present, `listen()` creates a TLS server.
|
|
370
|
+
* Setting `http2: true` additionally upgrades to HTTP/2 (`http2.createSecureServer`).
|
|
371
|
+
*/
|
|
372
|
+
export interface TlsOptions {
|
|
373
|
+
key: string | Buffer;
|
|
374
|
+
cert: string | Buffer;
|
|
375
|
+
/**
|
|
376
|
+
* When `true`, an HTTP/2 secure server (`http2.createSecureServer`) is
|
|
377
|
+
* created instead of an HTTPS/1.1 server. Requires `key` and `cert`.
|
|
378
|
+
* The existing middleware API is compatible with HTTP/2 request/response
|
|
379
|
+
* objects at runtime.
|
|
380
|
+
*/
|
|
381
|
+
http2?: boolean;
|
|
382
|
+
[key: string]: unknown;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* A middleware function. Receives the current request and response objects
|
|
386
|
+
* and a `next` callback to hand control to the next registered middleware.
|
|
387
|
+
*/
|
|
388
|
+
export type Middleware = (req: RouterRequest, res: RouterResponse, next: NextFunction) => void;
|
|
389
|
+
/**
|
|
390
|
+
* Callback used to pass control to the next matching middleware.
|
|
391
|
+
*
|
|
392
|
+
* When called **without** arguments (or with `undefined`), the router
|
|
393
|
+
* continues to the next matching layer as usual.
|
|
394
|
+
*
|
|
395
|
+
* When called **with** a non-null argument, the argument is treated as an
|
|
396
|
+
* error: remaining middleware and routes are skipped and the handler
|
|
397
|
+
* registered via {@link Router.onError} is invoked. If no error handler
|
|
398
|
+
* has been registered, a plain **500** response is sent.
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```ts
|
|
402
|
+
* router.use('/protected', (req, _res, next) => {
|
|
403
|
+
* if (!req.headers.authorization) return next(new Error('Unauthorized'));
|
|
404
|
+
* next(); // proceed normally
|
|
405
|
+
* });
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
export type NextFunction = (err?: unknown) => void;
|
|
409
|
+
/**
|
|
410
|
+
* Handler invoked when a middleware throws, an async middleware rejects,
|
|
411
|
+
* or `next(err)` is called with a non-null argument.
|
|
412
|
+
*
|
|
413
|
+
* Register it with {@link Router.onError}.
|
|
414
|
+
*
|
|
415
|
+
* @param err - The thrown value or the argument passed to `next()`.
|
|
416
|
+
* @param req - The current request.
|
|
417
|
+
* @param res - The current response (not yet ended — the handler must end it).
|
|
418
|
+
*/
|
|
419
|
+
export type ErrorHandler = (err: unknown, req: RouterRequest, res: RouterResponse) => void;
|
|
420
|
+
/**
|
|
421
|
+
* An ordered error-handling middleware, registered with {@link Router.error}.
|
|
422
|
+
*
|
|
423
|
+
* Unlike a regular {@link Middleware}, the error value is the **first**
|
|
424
|
+
* argument — a deliberate signal that this function runs on the error channel,
|
|
425
|
+
* not the normal request pipeline.
|
|
426
|
+
*
|
|
427
|
+
* The handler may either end the response (handling the error) or call `next`
|
|
428
|
+
* to pass control along the error channel:
|
|
429
|
+
* - `next()` — forward the **same** error to the next error middleware.
|
|
430
|
+
* - `next(newErr)` — forward a **replacement** error instead.
|
|
431
|
+
*
|
|
432
|
+
* When the error-middleware chain is exhausted without ending the response,
|
|
433
|
+
* the router falls back to the {@link Router.onError} handler (if any), and
|
|
434
|
+
* otherwise bubbles the error to the parent router's error channel. A
|
|
435
|
+
* top-level router with no handler sends a plain **500**.
|
|
436
|
+
*
|
|
437
|
+
* @param err - The thrown value, rejection reason, or `next(err)` argument.
|
|
438
|
+
* @param req - The current request.
|
|
439
|
+
* @param res - The current response (not yet ended — the handler must end it
|
|
440
|
+
* unless it forwards via `next`).
|
|
441
|
+
* @param next - Forward control along the error channel.
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* ```ts
|
|
445
|
+
* app.error((err, _req, res, next) => {
|
|
446
|
+
* if ((err as any)?.status === 404) return res.status(404).end('Not here');
|
|
447
|
+
* next(err); // not ours — let the next handler (or the parent) deal with it
|
|
448
|
+
* });
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
export type ErrorMiddleware = (err: unknown, req: RouterRequest, res: RouterResponse, next: NextFunction) => void;
|
|
452
|
+
/**
|
|
453
|
+
* A value that can be registered as a route handler: a single `Middleware`
|
|
454
|
+
* function, a `Router` instance (whose `listener` will be used), or an array
|
|
455
|
+
* of either. Arrays may not be nested.
|
|
456
|
+
*/
|
|
457
|
+
export type MiddlewareArg = Middleware | Router | (Middleware | Router)[];
|
|
458
|
+
/**
|
|
459
|
+
* Sanitised description of a single registered route, as returned by
|
|
460
|
+
* {@link Router.routes}.
|
|
461
|
+
*/
|
|
462
|
+
export interface RouteInfo {
|
|
463
|
+
/** HTTP method this layer is restricted to, or `null` for any method. */
|
|
464
|
+
method: string | null;
|
|
465
|
+
/** The original path pattern as supplied by the caller. */
|
|
466
|
+
path: string | RegExp;
|
|
467
|
+
/**
|
|
468
|
+
* `true` for prefix-style (`use`) registrations where the matched prefix
|
|
469
|
+
* is stripped from `req.path`; `false` for exact-method routes.
|
|
470
|
+
*/
|
|
471
|
+
stripPath: boolean;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Internal representation of a single registered route.
|
|
475
|
+
* One layer is created per middleware function for every `router.get(...)` /
|
|
476
|
+
* `router.use(...)` etc. call, and stored in the route table.
|
|
477
|
+
*/
|
|
478
|
+
export interface Layer {
|
|
479
|
+
/** HTTP method this layer is restricted to, or `null` for any method. */
|
|
480
|
+
method: string | null;
|
|
481
|
+
/** The original path pattern supplied by the caller. */
|
|
482
|
+
path: string | RegExp;
|
|
483
|
+
/**
|
|
484
|
+
* The compiled `RegExp` used for matching, regardless of whether the
|
|
485
|
+
* original `path` was a plain string, a glob, or already a `RegExp`.
|
|
486
|
+
*
|
|
487
|
+
* - Plain strings (e.g. `'/users/:id'`) are compiled with named capture
|
|
488
|
+
* groups so that `:id` becomes `(?<id>[^/]+)`.
|
|
489
|
+
* - Glob strings (e.g. `'/**\/*.php'`) are compiled following `.gitignore`
|
|
490
|
+
* wildcard rules.
|
|
491
|
+
* - `RegExp` values are stored as-is; named groups are surfaced directly
|
|
492
|
+
* as route parameters.
|
|
493
|
+
*/
|
|
494
|
+
regex: RegExp;
|
|
495
|
+
/**
|
|
496
|
+
* When `true`, the portion of `req.path` consumed by `layer.regex` is
|
|
497
|
+
* stripped before invoking the middleware, exposing only the remaining
|
|
498
|
+
* suffix to nested routers.
|
|
499
|
+
*
|
|
500
|
+
* Set to `true` for prefix-style registrations (`use`) and
|
|
501
|
+
* `false` for exact-method routes (`all`, `get`, `post`, etc.). Stripping on
|
|
502
|
+
* exact-match routes would break chained middlewares sharing the same path,
|
|
503
|
+
* because each subsequent layer would see a truncated path that no longer
|
|
504
|
+
* matches its own pattern.
|
|
505
|
+
*/
|
|
506
|
+
stripPath: boolean;
|
|
507
|
+
/** The middleware function to invoke when the layer matches. */
|
|
508
|
+
middleware: Middleware;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Fluent helper returned by {@link Router.route} that registers several
|
|
512
|
+
* HTTP-method handlers against a single, cached path.
|
|
513
|
+
*
|
|
514
|
+
* Each method forwards to the equivalent {@link Router} registration function
|
|
515
|
+
* with the captured path and returns the same builder, so calls can be chained.
|
|
516
|
+
*
|
|
517
|
+
* ```ts
|
|
518
|
+
* app.route('/users/:id')
|
|
519
|
+
* .get(getUser)
|
|
520
|
+
* .put(replaceUser)
|
|
521
|
+
* .delete(removeUser);
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
export interface RouteBuilder {
|
|
525
|
+
/** Register middleware for all HTTP methods (see {@link Router.all}). */
|
|
526
|
+
all(...args: MiddlewareArg[]): RouteBuilder;
|
|
527
|
+
/** Register middleware for `GET` requests. */
|
|
528
|
+
get(...args: MiddlewareArg[]): RouteBuilder;
|
|
529
|
+
/** Register middleware for `PUT` requests. */
|
|
530
|
+
put(...args: MiddlewareArg[]): RouteBuilder;
|
|
531
|
+
/** Register middleware for `POST` requests. */
|
|
532
|
+
post(...args: MiddlewareArg[]): RouteBuilder;
|
|
533
|
+
/** Register middleware for `DELETE` requests. */
|
|
534
|
+
delete(...args: MiddlewareArg[]): RouteBuilder;
|
|
535
|
+
/** Register middleware for `PATCH` requests. */
|
|
536
|
+
patch(...args: MiddlewareArg[]): RouteBuilder;
|
|
537
|
+
/** Register middleware for `HEAD` requests. */
|
|
538
|
+
head(...args: MiddlewareArg[]): RouteBuilder;
|
|
539
|
+
/** Register middleware for `OPTIONS` requests. */
|
|
540
|
+
options(...args: MiddlewareArg[]): RouteBuilder;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* The public interface of the object returned by `createRouter()`.
|
|
544
|
+
*
|
|
545
|
+
* All route-registration methods share the same uniform signature: a mandatory
|
|
546
|
+
* `path` argument followed by any number of `MiddlewareArg` values.
|
|
547
|
+
* A `MiddlewareArg` may be a `Middleware` function, a `Router` instance
|
|
548
|
+
* (whose `listener` is used automatically), or an array of either.
|
|
549
|
+
*/
|
|
550
|
+
export interface Router {
|
|
551
|
+
/**
|
|
552
|
+
* The path prefix this router was created with, if any.
|
|
553
|
+
*
|
|
554
|
+
* Set by passing a string as the first argument to `createRouter()`:
|
|
555
|
+
* ```ts
|
|
556
|
+
* const v1 = createRouter('/api/v1');
|
|
557
|
+
* console.log(v1.prefix); // '/api/v1'
|
|
558
|
+
* ```
|
|
559
|
+
* When a prefixed router is passed to `parent.use(v1)`, the parent uses
|
|
560
|
+
* this prefix as the mount path automatically.
|
|
561
|
+
*/
|
|
562
|
+
readonly prefix?: string;
|
|
563
|
+
/**
|
|
564
|
+
* Register prefix-style middleware scoped to `path`.
|
|
565
|
+
*
|
|
566
|
+
* The matched path prefix is stripped from `req.path` before the middleware
|
|
567
|
+
* runs, so nested routers only see the remaining suffix.
|
|
568
|
+
* Equivalent to Express's `app.use()`.
|
|
569
|
+
*
|
|
570
|
+
* **No-path shortcut:** when the first argument is a `Router` or `Middleware`
|
|
571
|
+
* (not a string or RegExp), the path defaults to the router's own
|
|
572
|
+
* {@link prefix} (or `'/'` if no prefix was set). This lets you mount a
|
|
573
|
+
* prefixed sub-router without repeating the path:
|
|
574
|
+
*
|
|
575
|
+
* ```ts
|
|
576
|
+
* const v1 = createRouter('/api/v1');
|
|
577
|
+
* v1.get('/users', handler);
|
|
578
|
+
* app.use(v1); // same as app.use('/api/v1', v1)
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
use(path: string | RegExp | MiddlewareArg, ...args: MiddlewareArg[]): void;
|
|
582
|
+
/**
|
|
583
|
+
* Register middleware for all HTTP methods without stripping `req.path`.
|
|
584
|
+
* Unlike `use`, the full path remains visible to every chained middleware.
|
|
585
|
+
*/
|
|
586
|
+
all(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
587
|
+
/** Register middleware for `GET` requests. */
|
|
588
|
+
get(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
589
|
+
/** Register middleware for `PUT` requests. */
|
|
590
|
+
put(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
591
|
+
/** Register middleware for `POST` requests. */
|
|
592
|
+
post(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
593
|
+
/** Register middleware for `DELETE` requests. */
|
|
594
|
+
delete(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
595
|
+
/** Register middleware for `PATCH` requests. */
|
|
596
|
+
patch(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
597
|
+
/**
|
|
598
|
+
* Register middleware for `HEAD` requests.
|
|
599
|
+
*
|
|
600
|
+
* Note: `HEAD` requests are already served automatically by a matching `GET`
|
|
601
|
+
* route (with the body suppressed). Register a `head()` handler only when
|
|
602
|
+
* you need `HEAD`-specific behaviour; if both exist, the one registered first
|
|
603
|
+
* wins.
|
|
604
|
+
*/
|
|
605
|
+
head(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
606
|
+
/**
|
|
607
|
+
* Register middleware for `OPTIONS` requests.
|
|
608
|
+
*
|
|
609
|
+
* Takes precedence over the automatic `204` + `Allow` response, which only
|
|
610
|
+
* fires when no `OPTIONS` layer (or `cors()`/`use()` middleware) handled the
|
|
611
|
+
* request.
|
|
612
|
+
*/
|
|
613
|
+
options(path: string | RegExp, ...args: MiddlewareArg[]): void;
|
|
614
|
+
/**
|
|
615
|
+
* Return a {@link RouteBuilder} bound to `path` for registering several
|
|
616
|
+
* HTTP-method handlers without repeating the path.
|
|
617
|
+
*
|
|
618
|
+
* The builder simply forwards each call to the matching method-registration
|
|
619
|
+
* function (`get`, `post`, …) with the cached `path`, so the routing
|
|
620
|
+
* behaviour is identical to calling those methods directly.
|
|
621
|
+
*
|
|
622
|
+
* @param path - Path pattern shared by all handlers registered on the builder.
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```ts
|
|
626
|
+
* app.route('/users/:id')
|
|
627
|
+
* .get(getUser)
|
|
628
|
+
* .put(replaceUser)
|
|
629
|
+
* .delete(removeUser);
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
route(path: string | RegExp): RouteBuilder;
|
|
633
|
+
/**
|
|
634
|
+
* Register a global error handler for this router.
|
|
635
|
+
*
|
|
636
|
+
* The handler is called whenever:
|
|
637
|
+
* - A middleware throws synchronously.
|
|
638
|
+
* - An `async` middleware returns a rejected `Promise`.
|
|
639
|
+
* - Any middleware calls `next(err)` with a non-null argument.
|
|
640
|
+
*
|
|
641
|
+
* Only one handler is active at a time; subsequent calls replace the
|
|
642
|
+
* previous one. The handler **must** end the response.
|
|
643
|
+
*
|
|
644
|
+
* @example
|
|
645
|
+
* ```ts
|
|
646
|
+
* app.onError((err, _req, res) => {
|
|
647
|
+
* const status = (err as any)?.status ?? 500;
|
|
648
|
+
* res.status(status).json({ error: String(err) });
|
|
649
|
+
* });
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
onError(handler: ErrorHandler): void;
|
|
653
|
+
/**
|
|
654
|
+
* Register an ordered error-handling middleware for this router.
|
|
655
|
+
*
|
|
656
|
+
* Error middlewares run, in registration order, whenever a middleware throws,
|
|
657
|
+
* an `async` middleware rejects, or `next(err)` is called. Each handler may
|
|
658
|
+
* end the response or call `next` to forward control along the error channel
|
|
659
|
+
* (see {@link ErrorMiddleware}).
|
|
660
|
+
*
|
|
661
|
+
* Resolution order when an error occurs:
|
|
662
|
+
* 1. Every `error()` handler in turn, until one ends the response.
|
|
663
|
+
* 2. If the chain is exhausted, the {@link Router.onError} fallback (if set).
|
|
664
|
+
* 3. Otherwise the error **bubbles to the parent router** (the one that
|
|
665
|
+
* mounted this router via `use()`), entering its error channel.
|
|
666
|
+
* 4. A top-level router with no handler sends a plain **500**.
|
|
667
|
+
*
|
|
668
|
+
* This bubbling is what lets a single error handler on the root router catch
|
|
669
|
+
* failures raised deep inside nested sub-routers.
|
|
670
|
+
*
|
|
671
|
+
* @example
|
|
672
|
+
* ```ts
|
|
673
|
+
* // Child: handle only what it owns, let the rest bubble up.
|
|
674
|
+
* child.error((err, _req, res, next) => {
|
|
675
|
+
* if ((err as any)?.code === 'CHILD') return res.status(400).end('bad');
|
|
676
|
+
* next(err);
|
|
677
|
+
* });
|
|
678
|
+
*
|
|
679
|
+
* // Root: final safety net for everything that bubbled up.
|
|
680
|
+
* app.error((err, _req, res) =>
|
|
681
|
+
* res.status((err as any)?.status ?? 500).json({ error: String(err) }));
|
|
682
|
+
* ```
|
|
683
|
+
*/
|
|
684
|
+
error(handler: ErrorMiddleware): void;
|
|
685
|
+
/**
|
|
686
|
+
* Return a read-only snapshot of all routes registered on this router.
|
|
687
|
+
*
|
|
688
|
+
* Useful for tooling, documentation generation, and debugging. The array
|
|
689
|
+
* is a fresh copy — mutating it does not affect the live route table.
|
|
690
|
+
*
|
|
691
|
+
* @returns An array of {@link RouteInfo} objects, one per registered layer,
|
|
692
|
+
* in registration order.
|
|
693
|
+
*
|
|
694
|
+
* @example
|
|
695
|
+
* ```ts
|
|
696
|
+
* app.get('/users', handler);
|
|
697
|
+
* app.post('/users', handler);
|
|
698
|
+
* console.log(app.routes());
|
|
699
|
+
* // [
|
|
700
|
+
* // { method: 'GET', path: '/users', stripPath: false },
|
|
701
|
+
* // { method: 'POST', path: '/users', stripPath: false },
|
|
702
|
+
* // ]
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
routes(): RouteInfo[];
|
|
706
|
+
/**
|
|
707
|
+
* Gracefully shut down the HTTP(S) server created by `router.listen()`.
|
|
708
|
+
*
|
|
709
|
+
* Stops accepting new connections and waits for all existing connections to
|
|
710
|
+
* close naturally. After `timeout` milliseconds, any remaining sockets are
|
|
711
|
+
* forcibly destroyed so the process is not kept alive indefinitely.
|
|
712
|
+
*
|
|
713
|
+
* If `router.listen()` was never called on this router (e.g. it is a
|
|
714
|
+
* sub-router mounted inside a parent), `shutdown()` resolves immediately
|
|
715
|
+
* without doing anything.
|
|
716
|
+
*
|
|
717
|
+
* @param timeout - Grace period in milliseconds before sockets are forcibly
|
|
718
|
+
* destroyed. Defaults to `5000`. Pass `0` to skip the forced teardown.
|
|
719
|
+
* @returns A `Promise` that resolves when the server has fully stopped.
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```ts
|
|
723
|
+
* const app = createRouter();
|
|
724
|
+
* app.listen(3000);
|
|
725
|
+
* process.on('SIGTERM', () => app.shutdown(10_000));
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
shutdown(timeout?: number): Promise<void>;
|
|
729
|
+
/**
|
|
730
|
+
* Start listening on the given port and return the underlying server instance.
|
|
731
|
+
*
|
|
732
|
+
* When `opts` contains both `key` and `cert`:
|
|
733
|
+
* - An **HTTPS** server is created (TLS, HTTP/1.1).
|
|
734
|
+
* - Setting `opts.http2 = true` additionally upgrades to **HTTP/2**
|
|
735
|
+
* (`http2.createSecureServer`).
|
|
736
|
+
*
|
|
737
|
+
* When `opts` is absent or contains neither key nor cert, a plain **HTTP**
|
|
738
|
+
* server is used.
|
|
739
|
+
*
|
|
740
|
+
* @returns The underlying server instance. Use it for graceful shutdown,
|
|
741
|
+
* port discovery, or attaching additional event listeners.
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* ```ts
|
|
745
|
+
* // Discover the OS-assigned ephemeral port
|
|
746
|
+
* const server = router.listen(0, () => {
|
|
747
|
+
* const { port } = server.address() as AddressInfo;
|
|
748
|
+
* console.log(`Listening on port ${port}`);
|
|
749
|
+
* });
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
listen(port: number, opts?: TlsOptions | (() => void), cb?: () => void): http.Server | https.Server | http2.Http2SecureServer;
|
|
753
|
+
/**
|
|
754
|
+
* The underlying `(req, res, next)` function, allowing this router to be
|
|
755
|
+
* mounted as middleware inside another router:
|
|
756
|
+
* `parent.use('/api', child)`.
|
|
757
|
+
*/
|
|
758
|
+
readonly listener: Middleware;
|
|
759
|
+
}
|
|
760
|
+
//# sourceMappingURL=router-types.d.ts.map
|