@dunx/http 3.2.0 → 3.3.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/dist/inspect.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  import { type Dependency, type ModuleRef } from '@dunx/core';
2
+ /**
3
+ * Routes and gateways read off the module graph, constructing nothing. The
4
+ * traversal is `@dunx/core`'s; here is the half needing this package's metadata -
5
+ * route markers, guards, `@Roles`/`@Public`, the gateway marker.
6
+ *
7
+ * `discoverRoutes` walks a prototype chain, and
8
+ * `Object.create(Controller.prototype)` is that chain with nothing behind it, so
9
+ * no constructor or dependency of one has to exist.
10
+ */
2
11
  export interface RouteInputs {
3
12
  readonly body?: string;
4
13
  readonly query?: string;
@@ -3,34 +3,17 @@
3
3
  *
4
4
  * The barrel was a semver promise 173 symbols wide, which is more than this
5
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.
6
+ * against. What is here is the discovery readers and route metadata that
7
+ * `@dunx/dashboard`, `@dunx/mcp`, `@dunx/openapi` and `@dunx/testing` import.
8
+ *
9
+ * It held 62 symbols and 50 had no importer, all of them reachable from inside
10
+ * this package by relative import. Add one back when a sibling needs it.
11
11
  *
12
12
  * No stability promise attaches to this subpath.
13
13
  */
14
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';
15
+ export { defaultStatusFor } from './route/marker.js';
17
16
  export { gatewaysOf, routesOf, type GatewayHandler, type GatewayNode, type RouteInputs, type RouteNode, } from './inspect.js';
18
17
  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';
18
+ export { embedJson } from './server/html.js';
19
+ export { isGateway } from './ws/marker.js';
package/dist/internal.js CHANGED
@@ -1,56 +1,22 @@
1
1
  // @bun
2
+ import"./chunk-bg0dr54z.js";
2
3
  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,
4
+ defaultStatusFor2,
5
+ ROLES2,
6
+ PUBLIC2,
7
+ HIDDEN2,
8
+ joinPath2,
9
+ discoverRoutes2,
10
+ isGateway2,
21
11
  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-y85wcdhw.js";
40
- import {
41
- backoffDelay,
42
- executeWithRetry,
43
- isJsonBody,
44
- isPlainObject,
45
- isRetryableStatus,
46
- retryAfterMs,
47
- safeStringify
48
- } from "./chunk-e8a9c6j2.js";
49
- import"./chunk-9x3evk19.js";
12
+ buildContext2
13
+ } from "./chunk-8939brh2.js";
50
14
  // src/inspect.ts
51
15
  import {
16
+ classOf,
52
17
  collectModules,
53
18
  dependenciesOf,
19
+ inertInstance,
54
20
  readControllers
55
21
  } from "@dunx/core";
56
22
  var vendorOf = (schema) => schema?.["~standard"]?.vendor;
@@ -65,7 +31,7 @@ var validatesIn = (options) => {
65
31
  };
66
32
  };
67
33
  var rolesIn = (route) => {
68
- const roles = route.meta?.get(ROLES.id);
34
+ const roles = route.meta?.get(ROLES2.id);
69
35
  if (roles === undefined || roles === null)
70
36
  return null;
71
37
  return (Array.isArray(roles) ? roles : [roles]).map(String);
@@ -76,20 +42,19 @@ var nodeFor = (route, module) => ({
76
42
  controller: route.controller,
77
43
  handler: route.handlerName,
78
44
  module,
79
- public: route.meta?.get(PUBLIC.id) === true,
45
+ public: route.meta?.get(PUBLIC2.id) === true,
80
46
  roles: rolesIn(route),
81
47
  guards: (route.guards ?? []).map((guard) => guard.name),
82
- hidden: route.meta?.get(HIDDEN.id) === true,
48
+ hidden: route.meta?.get(HIDDEN2.id) === true,
83
49
  validates: validatesIn(route.options),
84
50
  status: route.options?.status ?? null,
85
51
  responses: Object.keys(route.options?.response ?? {}).map(Number)
86
52
  });
87
53
  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));
54
+ return discoverRoutes2(inertInstance(controller)).map((route) => nodeFor(route, module.name));
90
55
  }));
91
56
  var gatewayFor = (ctor, module) => {
92
- const { name, path, handlers } = discoverGateway(Object.create(ctor.prototype));
57
+ const { name, path, handlers } = discoverGateway(inertInstance(ctor));
93
58
  return {
94
59
  name,
95
60
  path,
@@ -102,53 +67,16 @@ var gatewayFor = (ctor, module) => {
102
67
  }))
103
68
  };
104
69
  };
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)));
70
+ var gatewaysOf = (root) => collectModules(root).flatMap((module) => (module.options.providers ?? []).map((entry) => classOf(entry)?.ctor).filter((ctor) => ctor !== undefined).filter(isGateway2).map((ctor) => gatewayFor(ctor, module.name)));
71
+ // src/server/html.ts
72
+ var embedJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c");
111
73
  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,
74
+ buildContext2 as buildContext,
75
+ defaultStatusFor2 as defaultStatusFor,
76
+ discoverRoutes2 as discoverRoutes,
77
+ embedJson,
134
78
  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
79
+ isGateway2 as isGateway,
80
+ joinPath2 as joinPath,
81
+ routesOf
154
82
  };
@@ -1,106 +1,11 @@
1
1
  import type { BunRequest } from 'bun';
2
- import { type App, type AppOptions, type Ctor, type InjectionToken, type ModuleRef, type ShutdownHookOptions, type ShutdownSignal } from '@dunx/core';
2
+ import { ShutdownAware, type App, type Ctor, type InjectionToken, type ModuleRef } from '@dunx/core';
3
3
  import { type DiscoveredRoute } from '../route/discover.js';
4
4
  import type { WebSocketRuntime } from '../ws/adapter.js';
5
- import type { SocketLoggingOptions } from '../ws/logging.js';
6
- import type { SocketMiddleware } from '../ws/middleware.js';
7
- import type { PubSubRelay, RelayOptions } from '../ws/relay.js';
8
- import type { SocketOptions } from '../ws/socket.js';
9
5
  import type { CorsOptions } from './cors.js';
10
- import { type ErrorHandler } from './errors.js';
11
6
  import type { Middleware } from './middleware.js';
12
- import { type RequestLoggingOptions } from './request-logging.js';
13
7
  import { type AppSettings } from './settings.js';
14
- export interface HttpOptions extends AppOptions {
15
- readonly port?: number;
16
- /**
17
- * Prefixes every discovered route, the same thing {@link HttpApp.setGlobalPrefix}
18
- * does. Both exist: the method is what NestJS offers and what a script reaches
19
- * for, the field is what an `HttpOptionsProvider` can answer from validated
20
- * config. A later `setGlobalPrefix` call still wins, because it happens after.
21
- *
22
- * Explicitly `| undefined`, unlike the rest: a suite running one fixture both
23
- * prefixed and unprefixed passes a variable here, and under
24
- * `exactOptionalPropertyTypes` that would otherwise need a conditional spread.
25
- * "No prefix" and "absent" mean the same thing. `@dunx/testing` relies on it.
26
- */
27
- readonly prefix?: string | undefined;
28
- /** Mounts an `OPTIONS` preflight per path, as {@link HttpApp.enableCors} does. */
29
- readonly cors?: CorsOptions;
30
- /** `app.set('trust proxy', ...)` as a field. */
31
- readonly trustProxy?: boolean;
32
- /**
33
- * Calls `enableShutdownHooks` at construction. `true` takes the default signals;
34
- * an object names them and tunes the force-exit.
35
- */
36
- readonly shutdownHooks?: boolean | {
37
- readonly signals?: readonly ShutdownSignal[];
38
- readonly options?: ShutdownHookOptions;
39
- };
40
- /** Resolved from the container, so middleware can inject(). */
41
- readonly middleware?: readonly Ctor<Middleware>[];
42
- /**
43
- * Replaces the default mapper. Prefer an `ErrorFilter` class over a bare
44
- * `ErrorMapper`: a class is resolved from the container and can inject.
45
- */
46
- readonly onError?: ErrorHandler;
47
- /**
48
- * One structured entry per request, on by default and outermost, so a request
49
- * a guard rejected is still logged with the status it got.
50
- * See {@link RequestLoggingMiddleware}.
51
- */
52
- readonly requestLogging?: boolean | RequestLoggingOptions;
53
- /**
54
- * Count requests and time them per route, readable through
55
- * {@link RequestMetrics}. Off by default; `+35.2 ns` per request when
56
- * `requestLogging` is on, because the entry it already builds shares the
57
- * timing. With `requestLogging: false` a `MetricsMiddleware` pays for its own
58
- * `.then` instead, at +175.9 ns.
59
- */
60
- readonly metrics?: boolean;
61
- /**
62
- * One entry at `listen()` naming every route and gateway served. On by default,
63
- * and switched separately from `requestLogging`: one is per process, the other
64
- * per request. `@dunx/testing` defaults it off.
65
- */
66
- readonly bootLogging?: boolean;
67
- /** Bun's `websocket` options, plus where a throwing handler goes. Server-wide;
68
- * gateways themselves are declared in `@Module({ providers })`. */
69
- readonly websocket?: SocketOptions;
70
- /**
71
- * The socket half of `middleware`. Each entry wraps every dispatched gateway
72
- * handler; `socketLogging`'s runs outermost, ahead of anything here.
73
- */
74
- readonly socketMiddleware?: readonly Ctor<SocketMiddleware>[];
75
- /**
76
- * One structured entry per socket frame, on by default at `debug` - a gateway
77
- * can take a frame per connection per tick, so it writes nothing until an app
78
- * lowers its level. See {@link SocketLoggingMiddleware}.
79
- */
80
- readonly socketLogging?: boolean | SocketLoggingOptions;
81
- /**
82
- * Multi-node websocket fan-out. Absent means `PubSub` publishes to this process
83
- * only. Anything with `publish` and `subscribe` fits; one that has to come out
84
- * of the container goes through `app.get(PubSub).relayThrough(...)` instead.
85
- */
86
- readonly relay?: PubSubRelay;
87
- /** The broker channel the relay carries frames on. @default 'dunx:ws' */
88
- readonly relayChannel?: string;
89
- /**
90
- * How hard to retry a failed subscribe. Bounded, doubling, on an unref'd timer,
91
- * so a broker that never returns cannot hold the process open.
92
- */
93
- readonly relayResubscribe?: RelayOptions['resubscribe'];
94
- /**
95
- * What an unmatched path looks like to global middleware. `'guarded'` gives the
96
- * miss no route metadata, so a global guard refuses it and a prober cannot tell
97
- * a 404 from a 401. `'public'` reports it as `@Public()` for a conventional 404.
98
- * Either way `UNMATCHED` is set, which no real route sets.
99
- *
100
- * @default 'guarded'
101
- */
102
- readonly notFound?: 'guarded' | 'public';
103
- }
8
+ import type { HttpOptions } from './options.js';
104
9
  /**
105
10
  * Everything below `listen()` configures the route table, built once when the
106
11
  * server binds. Calling any of them afterwards throws.
@@ -118,9 +23,11 @@ export interface HttpApp extends App {
118
23
  clientIp(req: BunRequest): string | undefined;
119
24
  /** Every gateway path this app upgrades on, exactly as mounted. */
120
25
  readonly gatewayPaths: readonly string[];
26
+ /** The gateway port's own url, `undefined` unless `gatewayPort` split them. */
27
+ readonly gatewayUrl: string | undefined;
121
28
  listen(port?: number): Promise<string>;
122
29
  }
123
- export declare class HttpApplication implements HttpApp {
30
+ export declare class HttpApplication extends ShutdownAware implements HttpApp {
124
31
  #private;
125
32
  /** Forwarded from the container so an app can log scope warnings at boot. */
126
33
  readonly warnings: readonly string[];
@@ -137,6 +44,8 @@ export declare class HttpApplication implements HttpApp {
137
44
  /** The one `Bun.serve` call. A gateway's upgrade is a native `GET` route in the
138
45
  * same table, so Bun's router matches it and no `fetch` handler is needed. */
139
46
  listen(port?: number): Promise<string>;
47
+ /** The gateway port's own url, when `gatewayPort` split them. */
48
+ get gatewayUrl(): string | undefined;
140
49
  /** Public so an operator can start draining without committing to a shutdown,
141
50
  * which is what a readiness probe wants during a rolling deploy. */
142
51
  drain(): Promise<void>;
@@ -146,5 +55,4 @@ export declare class HttpApplication implements HttpApp {
146
55
  * `closed` unresolved; failures are collected and thrown at the end.
147
56
  */
148
57
  shutdown(): Promise<void>;
149
- enableShutdownHooks(signals?: readonly ShutdownSignal[], options?: ShutdownHookOptions): this;
150
58
  }
@@ -0,0 +1,67 @@
1
+ import type { Server } from 'bun';
2
+ import type { SocketData } from '../ws/socket.js';
3
+ import type { WebSocketRuntime } from '../ws/adapter.js';
4
+ import type { RouteHandler } from './middleware.js';
5
+ import { type BunRoutes } from './routes.js';
6
+ /** What `listen()` computes and hands the binding, once the table is final. */
7
+ export interface BindingPlan {
8
+ readonly port: number;
9
+ readonly routes: BunRoutes;
10
+ readonly fetch: RouteHandler;
11
+ readonly websocket: WebSocketRuntime | undefined;
12
+ }
13
+ /** The protocol settings, fixed at construction. `undefined` leaves Bun's own
14
+ * default in place rather than restating it. */
15
+ export interface BindingProtocols {
16
+ readonly http2?: boolean | undefined;
17
+ readonly http1?: boolean | undefined;
18
+ /**
19
+ * A port of its own for the gateways. When set, the upgrade routes are not
20
+ * merged into the main table and a second `Bun.serve` takes them.
21
+ */
22
+ readonly gatewayPort?: number | undefined;
23
+ }
24
+ /**
25
+ * The two per-server counters a metrics reader wants, summed across however
26
+ * many servers are bound. `Server` satisfies it on its own, which is what an
27
+ * unsplit app hands over.
28
+ */
29
+ export interface ServerGauges {
30
+ readonly pendingRequests: number;
31
+ readonly pendingWebSockets: number;
32
+ }
33
+ export interface Bound {
34
+ readonly main: Server<SocketData>;
35
+ /** The server that owns the sockets, which is what `PubSub` publishes on. */
36
+ readonly sockets: Server<SocketData>;
37
+ readonly gauges: ServerGauges;
38
+ }
39
+ /**
40
+ * The one or two `Bun.serve` instances an app binds.
41
+ *
42
+ * One is the normal shape: a gateway's upgrade is a native `GET` route in the
43
+ * same table, so Bun's router matches it and nothing else is needed.
44
+ *
45
+ * Two exist for `gatewayPort`. A websocket upgrade is an HTTP/1.1 request, so a
46
+ * server with `http1: false` can serve HTTP/2 routes or gateways and never both;
47
+ * splitting the ports is what lets one app do both. The gateway server takes no
48
+ * protocol overrides, because refusing HTTP/1.x there is the thing being avoided.
49
+ *
50
+ * Both servers come from one container, which is the reason this is a second
51
+ * `Bun.serve` rather than a second `HttpFactory.create`: a second app would build
52
+ * a second container, and the gateways would inject different singletons than the
53
+ * controllers.
54
+ */
55
+ export declare class ServerBinding {
56
+ #private;
57
+ constructor(protocols: BindingProtocols);
58
+ bind(plan: BindingPlan): Bound;
59
+ get gatewayUrl(): string | undefined;
60
+ /**
61
+ * `force` is for the server that owns the sockets: a graceful stop waits for
62
+ * open connections, and a WebSocket never closes itself, so those clients see
63
+ * a 1006. Under a split the routes server holds none and stops gracefully, so
64
+ * an in-flight request there finishes rather than being cut off.
65
+ */
66
+ stop(force: boolean): Promise<void>;
67
+ }
@@ -1,6 +1,8 @@
1
1
  import { type ModuleRef } from '@dunx/core';
2
- import { type HttpApp, type HttpOptions } from './application.js';
3
- export type { HttpApp, HttpOptions } from './application.js';
2
+ import { type HttpApp } from './application.js';
3
+ import type { HttpOptions } from './options.js';
4
+ export type { HttpApp } from './application.js';
5
+ export type { HttpOptions } from './options.js';
4
6
  export declare class HttpFactory {
5
7
  #private;
6
8
  /**
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Serialise a value for a `<script type="application/json">` block.
3
+ *
4
+ * `<` is the only character that can end the data block early, and escaping it as
5
+ * `\u003c` keeps the text valid JSON - the parser sees the same document either
6
+ * way. `@dunx/dashboard` and `@dunx/openapi` both inline a model into a page they
7
+ * serve, and both had a copy of this; if the escaping ever proves insufficient,
8
+ * one fix should cover both pages.
9
+ */
10
+ export declare const embedJson: (value: unknown) => string;
@@ -1,5 +1,6 @@
1
1
  import { type HistogramSnapshot } from '@dunx/core';
2
- import type { BunRequest, Server } from 'bun';
2
+ import type { BunRequest } from 'bun';
3
+ import type { ServerGauges } from './binding.js';
3
4
  import type { RouteContext } from './context.js';
4
5
  import type { Middleware, Next } from './middleware.js';
5
6
  /** Every path Bun matched nothing for, collapsed into one series. */
@@ -53,8 +54,12 @@ export declare class RequestMetrics {
53
54
  * reflecting a deploy three days ago; who calls this is the app's decision.
54
55
  */
55
56
  reset(): void;
56
- /** Internal: `listen()` hands the bound server to the resolved singleton. */
57
- attach(server: Server<unknown>): void;
57
+ /**
58
+ * Internal: `listen()` hands the bound server's counters to the resolved
59
+ * singleton. Structural, so an app serving from two ports can hand over the
60
+ * sum rather than one server's half of it.
61
+ */
62
+ attach(server: ServerGauges): void;
58
63
  }
59
64
  /**
60
65
  * Whether `MetricsMiddleware` is the thing doing the observing.
@@ -1,5 +1,5 @@
1
1
  import type { Ctor } from '@dunx/core';
2
- import type { HttpOptions } from './application.js';
2
+ import type { HttpOptions } from './options.js';
3
3
  import type { SocketLoggingOptions } from '../ws/logging.js';
4
4
  import type { SocketMiddleware } from '../ws/middleware.js';
5
5
  import type { PubSubRelay, RelayOptions } from '../ws/relay.js';
@@ -86,6 +86,19 @@ export declare abstract class HttpOptionsProvider {
86
86
  /** Multi-node websocket fan-out. Absent publishes to this process only. */
87
87
  get relay(): PubSubRelay | undefined;
88
88
  get relayResubscribe(): RelayOptions['resubscribe'];
89
+ /**
90
+ * Serve HTTP/2 alongside HTTP/1.1 on the same port. `undefined` rather than
91
+ * `false` so an absent answer leaves Bun's own default in place instead of
92
+ * restating it. See {@link HttpOptions.http2}.
93
+ */
94
+ get http2(): boolean | undefined;
95
+ /** See {@link HttpOptions.http1}. `false` disables every gateway. */
96
+ get http1(): boolean | undefined;
97
+ /**
98
+ * A port of its own for the gateways, which is what makes `http1: false`
99
+ * usable with one. See {@link HttpOptions.gatewayPort}.
100
+ */
101
+ get gatewayPort(): number | undefined;
89
102
  }
90
103
  /**
91
104
  * The base itself, bound when no module bound a subclass. Concrete because the
@@ -0,0 +1,141 @@
1
+ import type { AppOptions, Ctor, ShutdownHookOptions, ShutdownSignal } from '@dunx/core';
2
+ import type { SocketLoggingOptions } from '../ws/logging.js';
3
+ import type { SocketMiddleware } from '../ws/middleware.js';
4
+ import type { PubSubRelay, RelayOptions } from '../ws/relay.js';
5
+ import type { SocketOptions } from '../ws/socket.js';
6
+ import type { CorsOptions } from './cors.js';
7
+ import type { ErrorHandler } from './errors.js';
8
+ import type { Middleware } from './middleware.js';
9
+ import type { RequestLoggingOptions } from './request-logging.js';
10
+ /**
11
+ * Every setting `HttpFactory.create` takes. Here rather than in
12
+ * `application.ts` so that file is the class it is named for.
13
+ *
14
+ * Each field has a twin on {@link HttpOptionsProvider}, which is the same
15
+ * settings answered from the container instead. The argument wins field by
16
+ * field; see `resolveHttpOptions`.
17
+ */
18
+ export interface HttpOptions extends AppOptions {
19
+ readonly port?: number;
20
+ /**
21
+ * Prefixes every discovered route, the same thing {@link HttpApp.setGlobalPrefix}
22
+ * does. Both exist: the method is what NestJS offers and what a script reaches
23
+ * for, the field is what an `HttpOptionsProvider` can answer from validated
24
+ * config. A later `setGlobalPrefix` call still wins, because it happens after.
25
+ *
26
+ * Explicitly `| undefined`, unlike the rest: a suite running one fixture both
27
+ * prefixed and unprefixed passes a variable here, and under
28
+ * `exactOptionalPropertyTypes` that would otherwise need a conditional spread.
29
+ * "No prefix" and "absent" mean the same thing. `@dunx/testing` relies on it.
30
+ */
31
+ readonly prefix?: string | undefined;
32
+ /** Mounts an `OPTIONS` preflight per path, as {@link HttpApp.enableCors} does. */
33
+ readonly cors?: CorsOptions;
34
+ /** `app.set('trust proxy', ...)` as a field. */
35
+ readonly trustProxy?: boolean;
36
+ /**
37
+ * Calls `enableShutdownHooks` at construction. `true` takes the default signals;
38
+ * an object names them and tunes the force-exit.
39
+ */
40
+ readonly shutdownHooks?: boolean | {
41
+ readonly signals?: readonly ShutdownSignal[];
42
+ readonly options?: ShutdownHookOptions;
43
+ };
44
+ /** Resolved from the container, so middleware can inject(). */
45
+ readonly middleware?: readonly Ctor<Middleware>[];
46
+ /**
47
+ * Replaces the default mapper. Prefer an `ErrorFilter` class over a bare
48
+ * `ErrorMapper`: a class is resolved from the container and can inject.
49
+ */
50
+ readonly onError?: ErrorHandler;
51
+ /**
52
+ * One structured entry per request, on by default and outermost, so a request
53
+ * a guard rejected is still logged with the status it got.
54
+ * See {@link RequestLoggingMiddleware}.
55
+ */
56
+ readonly requestLogging?: boolean | RequestLoggingOptions;
57
+ /**
58
+ * Count requests and time them per route, readable through
59
+ * {@link RequestMetrics}. Off by default; `+35.2 ns` per request when
60
+ * `requestLogging` is on, because the entry it already builds shares the
61
+ * timing. With `requestLogging: false` a `MetricsMiddleware` pays for its own
62
+ * `.then` instead, at +175.9 ns.
63
+ */
64
+ readonly metrics?: boolean;
65
+ /**
66
+ * One entry at `listen()` naming every route and gateway served. On by default,
67
+ * and switched separately from `requestLogging`: one is per process, the other
68
+ * per request. `@dunx/testing` defaults it off.
69
+ */
70
+ readonly bootLogging?: boolean;
71
+ /** Bun's `websocket` options, plus where a throwing handler goes. Server-wide;
72
+ * gateways themselves are declared in `@Module({ providers })`. */
73
+ readonly websocket?: SocketOptions;
74
+ /**
75
+ * The socket half of `middleware`. Each entry wraps every dispatched gateway
76
+ * handler; `socketLogging`'s runs outermost, ahead of anything here.
77
+ */
78
+ readonly socketMiddleware?: readonly Ctor<SocketMiddleware>[];
79
+ /**
80
+ * One structured entry per socket frame, on by default at `debug` - a gateway
81
+ * can take a frame per connection per tick, so it writes nothing until an app
82
+ * lowers its level. See {@link SocketLoggingMiddleware}.
83
+ */
84
+ readonly socketLogging?: boolean | SocketLoggingOptions;
85
+ /**
86
+ * Multi-node websocket fan-out. Absent means `PubSub` publishes to this process
87
+ * only. Anything with `publish` and `subscribe` fits; one that has to come out
88
+ * of the container goes through `app.get(PubSub).relayThrough(...)` instead.
89
+ */
90
+ readonly relay?: PubSubRelay;
91
+ /** The broker channel the relay carries frames on. @default 'dunx:ws' */
92
+ readonly relayChannel?: string;
93
+ /**
94
+ * How hard to retry a failed subscribe. Bounded, doubling, on an unref'd timer,
95
+ * so a broker that never returns cannot hold the process open.
96
+ */
97
+ readonly relayResubscribe?: RelayOptions['resubscribe'];
98
+ /**
99
+ * What an unmatched path looks like to global middleware. `'guarded'` gives the
100
+ * miss no route metadata, so a global guard refuses it and a prober cannot tell
101
+ * a 404 from a 401. `'public'` reports it as `@Public()` for a conventional 404.
102
+ * Either way `UNMATCHED` is set, which no real route sets.
103
+ *
104
+ * @default 'public'
105
+ */
106
+ readonly notFound?: 'guarded' | 'public';
107
+ /**
108
+ * Serve HTTP/2 on the same port, through the same routes and the same
109
+ * `fetch` fallback. Without TLS that is h2c: a client opening with the HTTP/2
110
+ * preface gets HTTP/2 and everyone else gets HTTP/1.1, which is what a proxy
111
+ * in front of the app speaks. Bun marks the option experimental.
112
+ *
113
+ * Gateways are unaffected and keep working, because a websocket upgrade is an
114
+ * HTTP/1.1 request and Bun serves both protocols on the one socket. There is
115
+ * no websocket over HTTP/2.
116
+ *
117
+ * @default false
118
+ */
119
+ readonly http2?: boolean;
120
+ /**
121
+ * Serve HTTP/1.1. `false` alongside `http2` refuses HTTP/1.x with a 505, which
122
+ * **disables every gateway**: a websocket upgrade is an HTTP/1.1 request, so
123
+ * nothing can connect to one. Only for a port that is HTTP/2 or nothing.
124
+ *
125
+ * @default true
126
+ */
127
+ readonly http1?: boolean;
128
+ /**
129
+ * A port of its own for the gateways. The routes keep {@link port} and the
130
+ * upgrades move here, on a second `Bun.serve` that takes no protocol
131
+ * overrides, so it speaks HTTP/1.1 whatever the main port is set to.
132
+ *
133
+ * This is what makes `http2` with `http1: false` usable: a websocket upgrade
134
+ * is an HTTP/1.1 request, so one port serves HTTP/2-only routes or gateways
135
+ * and never both. The pair without a `gatewayPort` is a boot error.
136
+ *
137
+ * `0` takes any free port and is the one value that does not warn when no
138
+ * gateway is declared. See docs/guide/19-deployment.md.
139
+ */
140
+ readonly gatewayPort?: number;
141
+ }
@@ -1,5 +1,5 @@
1
+ import type { HandlerMethod } from '@dunx/core';
1
2
  type GatewayTarget = abstract new (...args: never[]) => object;
2
- type HandlerMethod = (...args: never[]) => unknown;
3
3
  export declare const Gateway: (path?: string) => <T extends GatewayTarget>(target: T) => T;
4
4
  /** Runs before the socket exists. Return a `Response` to refuse the upgrade. */
5
5
  export declare const OnUpgrade: () => <T extends HandlerMethod>(value: T) => T;
@@ -1,6 +1,4 @@
1
1
  import { WsRelay } from './relay.js';
2
- /** The same fallback chain `Bun.SQL` uses when given no URL. */
3
- export declare const defaultPostgresRelayUrl: () => string;
4
2
  export interface PostgresRelayOptions {
5
3
  /** @default `$POSTGRES_URL`, `$DATABASE_URL`, then `postgres://localhost:5432` */
6
4
  readonly url?: string;