@dunx/http 2.5.0 → 3.0.1
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/README.md +31 -909
- package/dist/chunk-25g22350.js +58 -0
- package/dist/chunk-b699cfes.js +130 -0
- package/dist/chunk-f6pw36av.js +1299 -0
- package/dist/{chunk-jh7jk0bn.js → chunk-sz4pvqxy.js} +1 -57
- package/dist/client/module.d.ts +2 -7
- package/dist/client/service.d.ts +11 -22
- package/dist/client.d.ts +1 -2
- package/dist/client.js +12 -142
- package/dist/compression/compression.d.ts +7 -12
- package/dist/compression/options.d.ts +11 -23
- package/dist/health/indicators.d.ts +5 -10
- package/dist/index.d.ts +13 -21
- package/dist/index.js +102 -1484
- package/dist/internal.d.ts +36 -0
- package/dist/internal.js +154 -0
- package/dist/route/metadata.d.ts +6 -9
- package/dist/route/schema.d.ts +27 -55
- package/dist/server/application.d.ts +33 -89
- package/dist/server/client-address.d.ts +6 -13
- package/dist/server/errors.d.ts +24 -55
- package/dist/server/request-id.d.ts +6 -11
- package/dist/server/request-logging.d.ts +29 -101
- package/dist/server/routes.d.ts +7 -19
- package/dist/server/trace-context.d.ts +6 -10
- package/dist/static/files.d.ts +5 -13
- package/dist/static/module.d.ts +10 -19
- package/dist/throttle/guard.d.ts +6 -9
- package/dist/throttle/module.d.ts +6 -8
- package/dist/throttle/store.d.ts +11 -20
- package/dist/ws/middleware.d.ts +12 -21
- package/dist/ws/redis-relay.d.ts +9 -19
- package/package.json +6 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the framework calls on itself, and the only place it is exported from.
|
|
3
|
+
*
|
|
4
|
+
* The barrel was a semver promise 173 symbols wide, which is more than this
|
|
5
|
+
* package meant to make. What stays public there is the surface an app writes
|
|
6
|
+
* against:
|
|
7
|
+
* decorators, options, contracts, errors, modules and the metadata helpers a
|
|
8
|
+
* user's own guard reads. What is here is route-table construction, the
|
|
9
|
+
* middleware fold, the relay codec and the discovery readers - things
|
|
10
|
+
* `@dunx/dashboard`, `@dunx/mcp` and `@dunx/openapi` need and an app does not.
|
|
11
|
+
*
|
|
12
|
+
* No stability promise attaches to this subpath.
|
|
13
|
+
*/
|
|
14
|
+
export { discoverRoutes, joinPath, type DiscoveredRoute, } from './route/discover.js';
|
|
15
|
+
export { defaultStatusFor, type DefaultStatus, type RouteMeta, } from './route/marker.js';
|
|
16
|
+
export { guardsOf } from './route/metadata.js';
|
|
17
|
+
export { gatewaysOf, routesOf, type GatewayHandler, type GatewayNode, type RouteInputs, type RouteNode, } from './inspect.js';
|
|
18
|
+
export { buildContext } from './server/context.js';
|
|
19
|
+
export { preflight, withCors } from './server/cors.js';
|
|
20
|
+
export { isErrorFilter, toErrorMapper } from './server/errors.js';
|
|
21
|
+
export { compose } from './server/middleware.js';
|
|
22
|
+
export { assertNoCollisions, assertNoGatewayCollisions, buildRoutes, withUpgradeRoutes, type BunRoutes, type GuardResolver, type RouteMethod, type ServeRoutes, } from './server/routes.js';
|
|
23
|
+
export { normalizePrefix } from './static/options.js';
|
|
24
|
+
export { negotiate } from './compression/negotiate.js';
|
|
25
|
+
export { isCompressibleType } from './compression/options.js';
|
|
26
|
+
export { buildWebSocket, type UpgradeHandler, type WebSocketRuntime, } from './ws/adapter.js';
|
|
27
|
+
export { discoverGateway, discoverGateways, normalizePath, type DiscoveredGateway, type DiscoveredHandler, type Invoke, } from './ws/discover.js';
|
|
28
|
+
export { decode, encode } from './ws/envelope.js';
|
|
29
|
+
export { composeSocket, observe } from './ws/middleware.js';
|
|
30
|
+
export { HandlerKind, isGateway, type HandlerMeta } from './ws/marker.js';
|
|
31
|
+
export { defaultRelayUrl } from './ws/redis-relay.js';
|
|
32
|
+
export { decodeRelay, encodeRelay, type RelayFrame, type RelayPhase, } from './ws/relay.js';
|
|
33
|
+
export { buildGateways, buildRuntime, type GatewayRuntime, } from './ws/runtime.js';
|
|
34
|
+
export { HiddenHealthController } from './health/controller.js';
|
|
35
|
+
export { backoffDelay, executeWithRetry, isRetryableStatus, retryAfterMs, } from './client/retry.js';
|
|
36
|
+
export { isJsonBody, isPlainObject, safeStringify } from './client/json.js';
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
HIDDEN,
|
|
4
|
+
HandlerKind,
|
|
5
|
+
HiddenHealthController,
|
|
6
|
+
PUBLIC,
|
|
7
|
+
ROLES,
|
|
8
|
+
assertNoCollisions,
|
|
9
|
+
assertNoGatewayCollisions,
|
|
10
|
+
buildContext,
|
|
11
|
+
buildGateways,
|
|
12
|
+
buildRoutes,
|
|
13
|
+
buildRuntime,
|
|
14
|
+
buildWebSocket,
|
|
15
|
+
compose,
|
|
16
|
+
composeSocket,
|
|
17
|
+
decode,
|
|
18
|
+
decodeRelay,
|
|
19
|
+
defaultRelayUrl,
|
|
20
|
+
defaultStatusFor,
|
|
21
|
+
discoverGateway,
|
|
22
|
+
discoverGateways,
|
|
23
|
+
discoverRoutes,
|
|
24
|
+
encode,
|
|
25
|
+
encodeRelay,
|
|
26
|
+
guardsOf,
|
|
27
|
+
isCompressibleType,
|
|
28
|
+
isErrorFilter,
|
|
29
|
+
isGateway,
|
|
30
|
+
joinPath,
|
|
31
|
+
negotiate,
|
|
32
|
+
normalizePath,
|
|
33
|
+
normalizePrefix,
|
|
34
|
+
observe,
|
|
35
|
+
preflight,
|
|
36
|
+
toErrorMapper,
|
|
37
|
+
withCors,
|
|
38
|
+
withUpgradeRoutes
|
|
39
|
+
} from "./chunk-f6pw36av.js";
|
|
40
|
+
import {
|
|
41
|
+
backoffDelay,
|
|
42
|
+
executeWithRetry,
|
|
43
|
+
isJsonBody,
|
|
44
|
+
isPlainObject,
|
|
45
|
+
isRetryableStatus,
|
|
46
|
+
retryAfterMs,
|
|
47
|
+
safeStringify
|
|
48
|
+
} from "./chunk-b699cfes.js";
|
|
49
|
+
import"./chunk-sz4pvqxy.js";
|
|
50
|
+
// src/inspect.ts
|
|
51
|
+
import {
|
|
52
|
+
collectModules,
|
|
53
|
+
dependenciesOf,
|
|
54
|
+
readControllers
|
|
55
|
+
} from "@dunx/core";
|
|
56
|
+
var vendorOf = (schema) => schema?.["~standard"]?.vendor;
|
|
57
|
+
var validatesIn = (options) => {
|
|
58
|
+
const body = vendorOf(options?.body);
|
|
59
|
+
const query = vendorOf(options?.query);
|
|
60
|
+
const params = vendorOf(options?.params);
|
|
61
|
+
return {
|
|
62
|
+
...body === undefined ? {} : { body },
|
|
63
|
+
...query === undefined ? {} : { query },
|
|
64
|
+
...params === undefined ? {} : { params }
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
var rolesIn = (route) => {
|
|
68
|
+
const roles = route.meta?.get(ROLES.id);
|
|
69
|
+
if (roles === undefined || roles === null)
|
|
70
|
+
return null;
|
|
71
|
+
return (Array.isArray(roles) ? roles : [roles]).map(String);
|
|
72
|
+
};
|
|
73
|
+
var nodeFor = (route, module) => ({
|
|
74
|
+
method: route.method,
|
|
75
|
+
path: route.path,
|
|
76
|
+
controller: route.controller,
|
|
77
|
+
handler: route.handlerName,
|
|
78
|
+
module,
|
|
79
|
+
public: route.meta?.get(PUBLIC.id) === true,
|
|
80
|
+
roles: rolesIn(route),
|
|
81
|
+
guards: (route.guards ?? []).map((guard) => guard.name),
|
|
82
|
+
hidden: route.meta?.get(HIDDEN.id) === true,
|
|
83
|
+
validates: validatesIn(route.options),
|
|
84
|
+
status: route.options?.status ?? null,
|
|
85
|
+
responses: Object.keys(route.options?.response ?? {}).map(Number)
|
|
86
|
+
});
|
|
87
|
+
var routesOf = (root) => collectModules(root).flatMap((module) => readControllers(module).flatMap((controller) => {
|
|
88
|
+
const { prototype } = controller;
|
|
89
|
+
return discoverRoutes(Object.create(prototype)).map((route) => nodeFor(route, module.name));
|
|
90
|
+
}));
|
|
91
|
+
var gatewayFor = (ctor, module) => {
|
|
92
|
+
const { name, path, handlers } = discoverGateway(Object.create(ctor.prototype));
|
|
93
|
+
return {
|
|
94
|
+
name,
|
|
95
|
+
path,
|
|
96
|
+
module,
|
|
97
|
+
dependencies: dependenciesOf(ctor),
|
|
98
|
+
handlers: handlers.map((handler) => ({
|
|
99
|
+
kind: handler.kind,
|
|
100
|
+
event: handler.event ?? null,
|
|
101
|
+
method: handler.method
|
|
102
|
+
}))
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
var classOf = (entry) => {
|
|
106
|
+
if (typeof entry === "function")
|
|
107
|
+
return entry;
|
|
108
|
+
return entry.provider.kind === "class" ? entry.provider.ctor : undefined;
|
|
109
|
+
};
|
|
110
|
+
var gatewaysOf = (root) => collectModules(root).flatMap((module) => (module.options.providers ?? []).map(classOf).filter((ctor) => ctor !== undefined).filter(isGateway).map((ctor) => gatewayFor(ctor, module.name)));
|
|
111
|
+
export {
|
|
112
|
+
HandlerKind,
|
|
113
|
+
HiddenHealthController,
|
|
114
|
+
assertNoCollisions,
|
|
115
|
+
assertNoGatewayCollisions,
|
|
116
|
+
backoffDelay,
|
|
117
|
+
buildContext,
|
|
118
|
+
buildGateways,
|
|
119
|
+
buildRoutes,
|
|
120
|
+
buildRuntime,
|
|
121
|
+
buildWebSocket,
|
|
122
|
+
compose,
|
|
123
|
+
composeSocket,
|
|
124
|
+
decode,
|
|
125
|
+
decodeRelay,
|
|
126
|
+
defaultRelayUrl,
|
|
127
|
+
defaultStatusFor,
|
|
128
|
+
discoverGateway,
|
|
129
|
+
discoverGateways,
|
|
130
|
+
discoverRoutes,
|
|
131
|
+
encode,
|
|
132
|
+
encodeRelay,
|
|
133
|
+
executeWithRetry,
|
|
134
|
+
gatewaysOf,
|
|
135
|
+
guardsOf,
|
|
136
|
+
isCompressibleType,
|
|
137
|
+
isErrorFilter,
|
|
138
|
+
isGateway,
|
|
139
|
+
isJsonBody,
|
|
140
|
+
isPlainObject,
|
|
141
|
+
isRetryableStatus,
|
|
142
|
+
joinPath,
|
|
143
|
+
negotiate,
|
|
144
|
+
normalizePath,
|
|
145
|
+
normalizePrefix,
|
|
146
|
+
observe,
|
|
147
|
+
preflight,
|
|
148
|
+
retryAfterMs,
|
|
149
|
+
routesOf,
|
|
150
|
+
safeStringify,
|
|
151
|
+
toErrorMapper,
|
|
152
|
+
withCors,
|
|
153
|
+
withUpgradeRoutes
|
|
154
|
+
};
|
package/dist/route/metadata.d.ts
CHANGED
|
@@ -31,17 +31,14 @@ export declare const UNMATCHED: MetaKey<boolean>;
|
|
|
31
31
|
export declare const Roles: (...roles: readonly string[]) => <F extends object>(target: F) => F;
|
|
32
32
|
export declare const Public: () => <F extends object>(target: F) => F;
|
|
33
33
|
/**
|
|
34
|
-
* Route, but not documented. Valid on a method or
|
|
34
|
+
* Route, but not documented. Valid on a method or a class.
|
|
35
35
|
*
|
|
36
|
-
* The motivating case is a
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* invalid entry named after an internal class, next to the 45 paths
|
|
40
|
-
* `betterAuthDocument` describes properly.
|
|
36
|
+
* The motivating case is a wildcard mount: `@dunx/auth` routes `<basePath>/*` to
|
|
37
|
+
* better-auth's handler, and `*` is not an OpenAPI path template, so documenting
|
|
38
|
+
* it produced an invalid entry named after an internal class.
|
|
41
39
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* where the rest of the route metadata already is.
|
|
40
|
+
* Here rather than in `@dunx/openapi` so `@dunx/auth` need not depend on the
|
|
41
|
+
* documentation package to say a route is undocumented.
|
|
45
42
|
*/
|
|
46
43
|
export declare const ApiHidden: () => <F extends object>(target: F) => F;
|
|
47
44
|
/**
|
package/dist/route/schema.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { BunRequest } from 'bun';
|
|
2
2
|
import type { DefaultStatus, HttpMethod } from './marker.js';
|
|
3
3
|
/**
|
|
4
|
-
* Standard Schema v1, restated rather than depended on
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* dependencies. Zod 4, Valibot and ArkType already satisfy this shape, so any of
|
|
8
|
-
* them drops straight into a route's options.
|
|
4
|
+
* Standard Schema v1, restated rather than depended on: the spec is an interface,
|
|
5
|
+
* so restating it keeps `@dunx/http` at zero dependencies. Zod 4, Valibot and
|
|
6
|
+
* ArkType all satisfy this shape already.
|
|
9
7
|
*/
|
|
10
8
|
export interface StandardSchemaV1<In = unknown, Out = In> {
|
|
11
9
|
readonly '~standard': {
|
|
@@ -35,11 +33,8 @@ export interface StandardSchemaIssue {
|
|
|
35
33
|
/** The validated output of a schema - `InferOutput<typeof CreateNote>` is `Note`. */
|
|
36
34
|
export type InferOutput<S> = S extends StandardSchemaV1<unknown, infer Out> ? Out : never;
|
|
37
35
|
/**
|
|
38
|
-
* A JSON Schema, as JSON.
|
|
39
|
-
*
|
|
40
|
-
* Declared here rather than in `@dunx/openapi` because {@link RouteSchemas} names
|
|
41
|
-
* it and that package depends on this one, so this is the lowest common owner.
|
|
42
|
-
* `@dunx/openapi` re-exports it.
|
|
36
|
+
* A JSON Schema, as JSON. Declared here rather than in `@dunx/openapi` because
|
|
37
|
+
* {@link RouteSchemas} names it and that package depends on this one.
|
|
43
38
|
*/
|
|
44
39
|
export type JsonSchema = Readonly<Record<string, unknown>>;
|
|
45
40
|
/**
|
|
@@ -55,9 +50,7 @@ export interface RouteSchemas {
|
|
|
55
50
|
readonly status?: number;
|
|
56
51
|
/**
|
|
57
52
|
* What the route answers with, keyed by status code, in the same Standard
|
|
58
|
-
* Schema the request side takes
|
|
59
|
-
* hoists into `components/schemas` exactly as a request body does, and there is
|
|
60
|
-
* one contract for both directions.
|
|
53
|
+
* Schema the request side takes.
|
|
61
54
|
*
|
|
62
55
|
* ```ts
|
|
63
56
|
* const one = {
|
|
@@ -66,19 +59,9 @@ export interface RouteSchemas {
|
|
|
66
59
|
* } as const satisfies RouteSchemas;
|
|
67
60
|
* ```
|
|
68
61
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* instead: the verb decorators constrain it against the entry for the success
|
|
73
|
-
* status, so a handler answering with a different shape is a `TS1241` naming
|
|
74
|
-
* the mismatched property. See {@link Returns}. Nothing in the request path
|
|
75
|
-
* reads this key.
|
|
76
|
-
*
|
|
77
|
-
* A plain {@link JsonSchema} is accepted here too, and only here: a JSON Schema
|
|
78
|
-
* needs no conversion, so documenting a response costs no validator. `$id` names
|
|
79
|
-
* it, hoisting it into `components/schemas` the way `.meta({ id })` does for a
|
|
80
|
-
* zod schema. `body`, `query` and `params` still take a Standard Schema, because
|
|
81
|
-
* those are parsed.
|
|
62
|
+
* Never validated at runtime, checked at compile time against the success
|
|
63
|
+
* status. A plain {@link JsonSchema} is accepted here and only here, and `$id`
|
|
64
|
+
* hoists it into `components/schemas`.
|
|
82
65
|
*
|
|
83
66
|
* ```ts
|
|
84
67
|
* response: {
|
|
@@ -92,10 +75,8 @@ export interface RouteSchemas {
|
|
|
92
75
|
export type ResponseMap = Readonly<Record<number, StandardSchemaV1 | JsonSchema>>;
|
|
93
76
|
/**
|
|
94
77
|
* The handler's parameter type, derived from its own options object. It has to be
|
|
95
|
-
* written out
|
|
96
|
-
*
|
|
97
|
-
* (docs/architecture/constraints.md) - but every field type still comes from the schemas, so nothing
|
|
98
|
-
* is declared twice:
|
|
78
|
+
* written out: a standard method decorator can check a parameter's type but not
|
|
79
|
+
* contextually type an unannotated one (docs/architecture/constraints.md).
|
|
99
80
|
*
|
|
100
81
|
* ```ts
|
|
101
82
|
* const createNote = { body: CreateNote, status: HttpStatusCode.CREATED } as const;
|
|
@@ -125,8 +106,8 @@ export type Input<O extends RouteSchemas> = {
|
|
|
125
106
|
} : unknown);
|
|
126
107
|
/**
|
|
127
108
|
* The status a handler's return type is held to: an explicit `options.status`,
|
|
128
|
-
* else the verb's default. Widened to `number` without `as const`, which
|
|
129
|
-
*
|
|
109
|
+
* else the verb's default. Widened to `number` without `as const`, which turns
|
|
110
|
+
* the check off rather than misapplying it.
|
|
130
111
|
*/
|
|
131
112
|
type SuccessStatus<O extends RouteSchemas, M extends HttpMethod> = O extends {
|
|
132
113
|
status: infer S extends number;
|
|
@@ -138,39 +119,30 @@ type SuccessStatus<O extends RouteSchemas, M extends HttpMethod> = O extends {
|
|
|
138
119
|
*/
|
|
139
120
|
type Declared<S> = [InferOutput<S>] extends [never] ? unknown : Serialised<InferOutput<S>>;
|
|
140
121
|
/**
|
|
141
|
-
* The declared shape as JSON will present it
|
|
142
|
-
* array
|
|
122
|
+
* The declared shape as JSON will present it: the same shape with every array made
|
|
123
|
+
* readonly. `z.array()` infers a mutable `T[]`, so a method correctly returning
|
|
124
|
+
* `readonly User[]` would fail against a document it satisfies, and mutability
|
|
125
|
+
* does not survive `Response.json` anyway.
|
|
143
126
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* something that must not be mutated, would fail against a document it satisfies.
|
|
147
|
-
* Mutability does not survive `Response.json`, so it is not part of the contract.
|
|
148
|
-
*
|
|
149
|
-
* Only arrays need the rewrite; TypeScript already ignores a property's `readonly`
|
|
150
|
-
* modifier when checking assignability. The object branch is how nested arrays are
|
|
151
|
-
* reached, and functions are returned untouched because mapping over one would
|
|
152
|
-
* discard its call signature.
|
|
127
|
+
* Only arrays need it. The object branch reaches nested ones; functions are
|
|
128
|
+
* returned untouched, since mapping over one discards its call signature.
|
|
153
129
|
*/
|
|
154
130
|
type Serialised<T> = T extends readonly (infer E)[] ? readonly Serialised<E>[] : T extends (...args: never[]) => unknown ? T : T extends object ? {
|
|
155
131
|
readonly [K in keyof T]: Serialised<T[K]>;
|
|
156
132
|
} : T;
|
|
157
133
|
/**
|
|
158
|
-
* What a handler may return, given its
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* (docs/architecture/constraints.md), and that cuts both ways: this is the return
|
|
162
|
-
* half of the same guarantee `Input<O>` gives the parameter. Declaring
|
|
163
|
-
* `response: { 200: User }` stops being documentation a handler can contradict.
|
|
134
|
+
* What a handler may return, given its options object and its verb. The return
|
|
135
|
+
* half of the guarantee `Input<O>` gives the parameter, so `response: { 200: User }`
|
|
136
|
+
* stops being documentation a handler can contradict.
|
|
164
137
|
*
|
|
165
|
-
* `Response` is always allowed
|
|
166
|
-
*
|
|
167
|
-
* success status has no `response` entry.
|
|
138
|
+
* `Response` is always allowed, and so is a promise of either. Nothing is checked
|
|
139
|
+
* when the success status has no `response` entry.
|
|
168
140
|
*/
|
|
169
141
|
export type Returns<O extends RouteSchemas, M extends HttpMethod> = SuccessBody<O, M> | Response | Promise<SuccessBody<O, M> | Response>;
|
|
170
142
|
/**
|
|
171
|
-
* `infer R extends ResponseMap` is
|
|
172
|
-
*
|
|
173
|
-
*
|
|
143
|
+
* `infer R extends ResponseMap` is required: without it the narrowed `O` is
|
|
144
|
+
* `{ response: R } & O`, whose `response` no longer satisfies `RouteSchemas`, and
|
|
145
|
+
* `SuccessStatus<O, M>` fails with `TS2344`.
|
|
174
146
|
*/
|
|
175
147
|
type SuccessBody<O extends RouteSchemas, M extends HttpMethod> = O extends {
|
|
176
148
|
response: infer R extends ResponseMap;
|
|
@@ -16,111 +16,62 @@ export interface HttpOptions extends AppOptions {
|
|
|
16
16
|
/** Resolved from the container, so middleware can inject(). */
|
|
17
17
|
readonly middleware?: readonly Ctor<Middleware>[];
|
|
18
18
|
/**
|
|
19
|
-
* Replaces the default mapper.
|
|
20
|
-
*
|
|
21
|
-
* A bare `ErrorMapper` function, or an `ErrorFilter` **class** - which is the one
|
|
22
|
-
* to prefer, because a class is resolved from the container and can therefore
|
|
23
|
-
* inject the `Logger` or the config a real filter needs. A mapper cannot; dunx's
|
|
24
|
-
* own default has to be curried over its logger for exactly that reason.
|
|
25
|
-
*
|
|
26
|
-
* A filter with dependencies needs them bindable, the same rule `middleware`
|
|
27
|
-
* entries follow; one with none self-binds and needs no `providers` entry.
|
|
19
|
+
* Replaces the default mapper. Prefer an `ErrorFilter` class over a bare
|
|
20
|
+
* `ErrorMapper`: a class is resolved from the container and can inject.
|
|
28
21
|
*/
|
|
29
22
|
readonly onError?: ErrorHandler;
|
|
30
23
|
/**
|
|
31
|
-
* One structured entry per request, on by default
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* It is the **outermost** middleware, ahead of anything `middleware` declares,
|
|
35
|
-
* so a request rejected by a guard is still logged with the status it got.
|
|
24
|
+
* One structured entry per request, on by default and outermost, so a request
|
|
25
|
+
* a guard rejected is still logged with the status it got.
|
|
26
|
+
* See {@link RequestLoggingMiddleware}.
|
|
36
27
|
*/
|
|
37
28
|
readonly requestLogging?: boolean | RequestLoggingOptions;
|
|
38
29
|
/**
|
|
39
|
-
* One entry at `listen()` naming every route and gateway
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `false` removes it. Separate from `requestLogging` rather than sharing its
|
|
44
|
-
* switch: one is per request and one is per process, and silencing the noisy one
|
|
45
|
-
* is not a reason to lose the quiet one. `@dunx/testing` defaults it off, for the
|
|
46
|
-
* same reason it defaults request logging off.
|
|
30
|
+
* One entry at `listen()` naming every route and gateway served. On by default,
|
|
31
|
+
* and switched separately from `requestLogging`: one is per process, the other
|
|
32
|
+
* per request. `@dunx/testing` defaults it off.
|
|
47
33
|
*/
|
|
48
34
|
readonly bootLogging?: boolean;
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
* they live here next to `middleware` rather than on a module: gateways
|
|
52
|
-
* themselves are declared in `@Module({ providers })`.
|
|
53
|
-
*/
|
|
35
|
+
/** Bun's `websocket` options, plus where a throwing handler goes. Server-wide;
|
|
36
|
+
* gateways themselves are declared in `@Module({ providers })`. */
|
|
54
37
|
readonly websocket?: SocketOptions;
|
|
55
38
|
/**
|
|
56
|
-
* The socket half of `middleware
|
|
57
|
-
*
|
|
58
|
-
* Each entry wraps every dispatched gateway handler - open, each named message,
|
|
59
|
-
* the catch-all, close, drain, ping and pong - the way an HTTP middleware wraps a
|
|
60
|
-
* route. `socketLogging`'s middleware runs outermost, ahead of anything here.
|
|
39
|
+
* The socket half of `middleware`. Each entry wraps every dispatched gateway
|
|
40
|
+
* handler; `socketLogging`'s runs outermost, ahead of anything here.
|
|
61
41
|
*/
|
|
62
42
|
readonly socketMiddleware?: readonly Ctor<SocketMiddleware>[];
|
|
63
43
|
/**
|
|
64
|
-
* One structured entry per socket frame, on by default at
|
|
65
|
-
*
|
|
66
|
-
* {@link SocketLoggingMiddleware}.
|
|
67
|
-
*
|
|
68
|
-
* `debug` rather than request logging's `info`, because a gateway can take a
|
|
69
|
-
* frame per connection per tick. The default `ConsoleLogger` threshold is
|
|
70
|
-
* `info`, so this writes nothing until an app lowers its level or names a louder
|
|
71
|
-
* one here.
|
|
72
|
-
*
|
|
73
|
-
* Installing it also takes `SocketOptions.onError`'s `console.error` default out
|
|
74
|
-
* of the way: a middleware wraps the handler, so the failure is already reported
|
|
75
|
-
* through the `Logger` with the gateway and the event on it.
|
|
44
|
+
* One structured entry per socket frame, on by default at `debug` - a gateway
|
|
45
|
+
* can take a frame per connection per tick, so it writes nothing until an app
|
|
46
|
+
* lowers its level. See {@link SocketLoggingMiddleware}.
|
|
76
47
|
*/
|
|
77
48
|
readonly socketLogging?: boolean | SocketLoggingOptions;
|
|
78
49
|
/**
|
|
79
|
-
* Multi-node websocket fan-out. Absent
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* `new RedisRelay({ url })` is the batteries-included one. Anything with a
|
|
83
|
-
* `publish` and a `subscribe` fits, including `@dunx/infra`'s `RedisConnection`,
|
|
84
|
-
* which has to come out of the container and so goes through
|
|
85
|
-
* `app.get(PubSub).relayThrough(...)` instead of this option.
|
|
50
|
+
* Multi-node websocket fan-out. Absent means `PubSub` publishes to this process
|
|
51
|
+
* only. Anything with `publish` and `subscribe` fits; one that has to come out
|
|
52
|
+
* of the container goes through `app.get(PubSub).relayThrough(...)` instead.
|
|
86
53
|
*/
|
|
87
54
|
readonly relay?: PubSubRelay;
|
|
88
55
|
/** The broker channel the relay carries frames on. @default 'dunx:ws' */
|
|
89
56
|
readonly relayChannel?: string;
|
|
90
57
|
/**
|
|
91
|
-
* How hard to retry a subscribe
|
|
92
|
-
*
|
|
93
|
-
* broker that never comes back cannot hold the process open.
|
|
94
|
-
*
|
|
95
|
-
* Here rather than only on `relayThrough` because reaching for that to set one
|
|
96
|
-
* option means giving up `relay` above entirely - the two conflict, and the
|
|
97
|
-
* second to run throws `PubSub already relays`.
|
|
58
|
+
* How hard to retry a failed subscribe. Bounded, doubling, on an unref'd timer,
|
|
59
|
+
* so a broker that never returns cannot hold the process open.
|
|
98
60
|
*/
|
|
99
61
|
readonly relayResubscribe?: RelayOptions['resubscribe'];
|
|
100
62
|
/**
|
|
101
|
-
* What an unmatched path looks like to global middleware.
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* That is deliberate: a 404 on a miss while every real path answers 401 tells a
|
|
106
|
-
* prober which paths exist.
|
|
107
|
-
*
|
|
108
|
-
* `'public'` reports the miss as `@Public()`, so a guard honouring that flag
|
|
109
|
-
* passes it through to the conventional 404. The request is still logged and
|
|
110
|
-
* still gets a request id either way, which is the whole reason the fallback
|
|
111
|
-
* runs the middleware at all.
|
|
112
|
-
*
|
|
113
|
-
* A guard can discriminate under either setting: `UNMATCHED` is set on the miss
|
|
114
|
-
* and no real route ever sets it.
|
|
63
|
+
* What an unmatched path looks like to global middleware. `'guarded'` gives the
|
|
64
|
+
* miss no route metadata, so a global guard refuses it and a prober cannot tell
|
|
65
|
+
* a 404 from a 401. `'public'` reports it as `@Public()` for a conventional 404.
|
|
66
|
+
* Either way `UNMATCHED` is set, which no real route sets.
|
|
115
67
|
*
|
|
116
68
|
* @default 'guarded'
|
|
117
69
|
*/
|
|
118
70
|
readonly notFound?: 'guarded' | 'public';
|
|
119
71
|
}
|
|
120
72
|
/**
|
|
121
|
-
* Everything below `listen()` configures the route table,
|
|
122
|
-
*
|
|
123
|
-
* being quietly dropped.
|
|
73
|
+
* Everything below `listen()` configures the route table, built once when the
|
|
74
|
+
* server binds. Calling any of them afterwards throws.
|
|
124
75
|
*/
|
|
125
76
|
export interface HttpApp extends App {
|
|
126
77
|
/** Prefixes every discovered route. Last call wins. */
|
|
@@ -151,23 +102,16 @@ export declare class HttpApplication implements HttpApp {
|
|
|
151
102
|
setting<K extends keyof AppSettings>(key: K): AppSettings[K];
|
|
152
103
|
enableCors(options?: CorsOptions): this;
|
|
153
104
|
clientIp(req: BunRequest): string | undefined;
|
|
154
|
-
/**
|
|
155
|
-
*
|
|
156
|
-
* same table, so Bun's router - not a hand-written `fetch` fallback - is what
|
|
157
|
-
* matches an upgrade, and no `fetch` handler is needed at all.
|
|
158
|
-
*/
|
|
105
|
+
/** The one `Bun.serve` call. A gateway's upgrade is a native `GET` route in the
|
|
106
|
+
* same table, so Bun's router matches it and no `fetch` handler is needed. */
|
|
159
107
|
listen(port?: number): Promise<string>;
|
|
160
|
-
/**
|
|
161
|
-
*
|
|
162
|
-
* runs it. Public so an operator can start draining without committing to a
|
|
163
|
-
* shutdown, which is what a readiness probe wants during a rolling deploy.
|
|
164
|
-
*/
|
|
108
|
+
/** Public so an operator can start draining without committing to a shutdown,
|
|
109
|
+
* which is what a readiness probe wants during a rolling deploy. */
|
|
165
110
|
drain(): Promise<void>;
|
|
166
111
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* collected now and thrown once the whole teardown is over.
|
|
112
|
+
* Four phases in order, none skipped because an earlier one failed. A throwing
|
|
113
|
+
* drain hook used to abort before `server.stop()`, leaving the port open and
|
|
114
|
+
* `closed` unresolved; failures are collected and thrown at the end.
|
|
171
115
|
*/
|
|
172
116
|
shutdown(): Promise<void>;
|
|
173
117
|
enableShutdownHooks(signals?: readonly ShutdownSignal[], options?: ShutdownHookOptions): this;
|
|
@@ -4,20 +4,13 @@ export interface AddressSource {
|
|
|
4
4
|
readonly trustProxy: boolean | number;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* The client's address, honouring
|
|
7
|
+
* The client's address, honouring `'trust proxy'`. The address is counted from the
|
|
8
|
+
* right of `X-Forwarded-For` by the number of trusted hops, never from the left: a
|
|
9
|
+
* client can send anything, and only the entries a proxy appended carry weight.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* weight, and there are exactly as many of those as there are proxies in front of
|
|
13
|
-
* this server.
|
|
14
|
-
*
|
|
15
|
-
* Bound and exported by `HttpFactory`'s global wrapper module, so injecting it in a
|
|
16
|
-
* middleware or controller needs no registration and `app.clientIp(req)` is the same
|
|
17
|
-
* instance. That binding is not optional under module scoping: an unbound class
|
|
18
|
-
* self-binds into whichever scope asks first, so a second module injecting it was a
|
|
19
|
-
* boot error naming the first, and `listen()` could attach the server to an instance
|
|
20
|
-
* nothing else held.
|
|
11
|
+
* Bound by `HttpFactory`'s global wrapper, which is not optional under module
|
|
12
|
+
* scoping - an unbound class self-binds into whichever scope asks first, so a
|
|
13
|
+
* second module injecting it was a boot error.
|
|
21
14
|
*/
|
|
22
15
|
export declare class ClientAddress {
|
|
23
16
|
of(req: BunRequest): string | undefined;
|