@dunx/http 3.1.3 → 3.2.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 +14 -4
- package/dist/{chunk-25g22350.js → chunk-3j2n1n11.js} +22 -9
- package/dist/chunk-3nbj06q8.js +174 -0
- package/dist/client/options.d.ts +7 -13
- package/dist/client/service.d.ts +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/client.js +127 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1325 -110
- package/dist/inspect.d.ts +9 -0
- package/dist/internal.d.ts +8 -25
- package/dist/internal.js +11 -83
- package/dist/server/application.d.ts +10 -3
- package/dist/server/html.d.ts +10 -0
- package/dist/server/metrics.d.ts +80 -0
- package/dist/server/options-provider.d.ts +2 -0
- package/dist/server/request-logging.d.ts +26 -12
- package/dist/server/trace-context.d.ts +41 -10
- package/dist/ws/decorators.d.ts +1 -1
- package/dist/ws/postgres-relay.d.ts +0 -2
- package/dist/ws/relay.d.ts +8 -0
- package/package.json +7 -4
- package/dist/chunk-53cs6qek.js +0 -1316
- package/dist/chunk-7xrtwbx3.js +0 -130
- package/dist/server/request-id.d.ts +0 -22
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;
|
package/dist/internal.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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 {
|
|
20
|
-
export {
|
|
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
2
|
import {
|
|
3
3
|
HIDDEN,
|
|
4
|
-
HandlerKind,
|
|
5
|
-
HiddenHealthController,
|
|
6
4
|
PUBLIC,
|
|
7
5
|
ROLES,
|
|
8
|
-
assertNoCollisions,
|
|
9
|
-
assertNoGatewayCollisions,
|
|
10
6
|
buildContext,
|
|
11
|
-
buildGateways,
|
|
12
|
-
buildRoutes,
|
|
13
|
-
buildRuntime,
|
|
14
|
-
buildWebSocket,
|
|
15
|
-
compose,
|
|
16
|
-
composeSocket,
|
|
17
|
-
decode,
|
|
18
|
-
decodeRelay,
|
|
19
|
-
defaultRelayUrl,
|
|
20
7
|
defaultStatusFor,
|
|
21
8
|
discoverGateway,
|
|
22
|
-
discoverGateways,
|
|
23
9
|
discoverRoutes,
|
|
24
|
-
encode,
|
|
25
|
-
encodeRelay,
|
|
26
|
-
guardsOf,
|
|
27
|
-
isCompressibleType,
|
|
28
|
-
isErrorFilter,
|
|
29
10
|
isGateway,
|
|
30
|
-
joinPath
|
|
31
|
-
|
|
32
|
-
normalizePath,
|
|
33
|
-
normalizePrefix,
|
|
34
|
-
observe,
|
|
35
|
-
preflight,
|
|
36
|
-
toErrorMapper,
|
|
37
|
-
withCors,
|
|
38
|
-
withUpgradeRoutes
|
|
39
|
-
} from "./chunk-53cs6qek.js";
|
|
40
|
-
import {
|
|
41
|
-
backoffDelay,
|
|
42
|
-
executeWithRetry,
|
|
43
|
-
isJsonBody,
|
|
44
|
-
isPlainObject,
|
|
45
|
-
isRetryableStatus,
|
|
46
|
-
retryAfterMs,
|
|
47
|
-
safeStringify
|
|
48
|
-
} from "./chunk-7xrtwbx3.js";
|
|
11
|
+
joinPath
|
|
12
|
+
} from "./chunk-3nbj06q8.js";
|
|
49
13
|
import"./chunk-sz4pvqxy.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;
|
|
@@ -85,11 +51,10 @@ var nodeFor = (route, module) => ({
|
|
|
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
|
-
|
|
89
|
-
return discoverRoutes(Object.create(prototype)).map((route) => nodeFor(route, module.name));
|
|
54
|
+
return discoverRoutes(inertInstance(controller)).map((route) => nodeFor(route, module.name));
|
|
90
55
|
}));
|
|
91
56
|
var gatewayFor = (ctor, module) => {
|
|
92
|
-
const { name, path, handlers } = discoverGateway(
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
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(isGateway).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
74
|
buildContext,
|
|
118
|
-
buildGateways,
|
|
119
|
-
buildRoutes,
|
|
120
|
-
buildRuntime,
|
|
121
|
-
buildWebSocket,
|
|
122
|
-
compose,
|
|
123
|
-
composeSocket,
|
|
124
|
-
decode,
|
|
125
|
-
decodeRelay,
|
|
126
|
-
defaultRelayUrl,
|
|
127
75
|
defaultStatusFor,
|
|
128
|
-
discoverGateway,
|
|
129
|
-
discoverGateways,
|
|
130
76
|
discoverRoutes,
|
|
131
|
-
|
|
132
|
-
encodeRelay,
|
|
133
|
-
executeWithRetry,
|
|
77
|
+
embedJson,
|
|
134
78
|
gatewaysOf,
|
|
135
|
-
guardsOf,
|
|
136
|
-
isCompressibleType,
|
|
137
|
-
isErrorFilter,
|
|
138
79
|
isGateway,
|
|
139
|
-
isJsonBody,
|
|
140
|
-
isPlainObject,
|
|
141
|
-
isRetryableStatus,
|
|
142
80
|
joinPath,
|
|
143
|
-
|
|
144
|
-
normalizePath,
|
|
145
|
-
normalizePrefix,
|
|
146
|
-
observe,
|
|
147
|
-
preflight,
|
|
148
|
-
retryAfterMs,
|
|
149
|
-
routesOf,
|
|
150
|
-
safeStringify,
|
|
151
|
-
toErrorMapper,
|
|
152
|
-
withCors,
|
|
153
|
-
withUpgradeRoutes
|
|
81
|
+
routesOf
|
|
154
82
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
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 AppOptions, type Ctor, type InjectionToken, type ModuleRef, type ShutdownHookOptions, type ShutdownSignal } from '@dunx/core';
|
|
3
3
|
import { type DiscoveredRoute } from '../route/discover.js';
|
|
4
4
|
import type { WebSocketRuntime } from '../ws/adapter.js';
|
|
5
5
|
import type { SocketLoggingOptions } from '../ws/logging.js';
|
|
@@ -50,6 +50,14 @@ export interface HttpOptions extends AppOptions {
|
|
|
50
50
|
* See {@link RequestLoggingMiddleware}.
|
|
51
51
|
*/
|
|
52
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;
|
|
53
61
|
/**
|
|
54
62
|
* One entry at `listen()` naming every route and gateway served. On by default,
|
|
55
63
|
* and switched separately from `requestLogging`: one is per process, the other
|
|
@@ -112,7 +120,7 @@ export interface HttpApp extends App {
|
|
|
112
120
|
readonly gatewayPaths: readonly string[];
|
|
113
121
|
listen(port?: number): Promise<string>;
|
|
114
122
|
}
|
|
115
|
-
export declare class HttpApplication implements HttpApp {
|
|
123
|
+
export declare class HttpApplication extends ShutdownAware implements HttpApp {
|
|
116
124
|
#private;
|
|
117
125
|
/** Forwarded from the container so an app can log scope warnings at boot. */
|
|
118
126
|
readonly warnings: readonly string[];
|
|
@@ -138,5 +146,4 @@ export declare class HttpApplication implements HttpApp {
|
|
|
138
146
|
* `closed` unresolved; failures are collected and thrown at the end.
|
|
139
147
|
*/
|
|
140
148
|
shutdown(): Promise<void>;
|
|
141
|
-
enableShutdownHooks(signals?: readonly ShutdownSignal[], options?: ShutdownHookOptions): this;
|
|
142
149
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type HistogramSnapshot } from '@dunx/core';
|
|
2
|
+
import type { BunRequest, Server } from 'bun';
|
|
3
|
+
import type { RouteContext } from './context.js';
|
|
4
|
+
import type { Middleware, Next } from './middleware.js';
|
|
5
|
+
/** Every path Bun matched nothing for, collapsed into one series. */
|
|
6
|
+
export declare const UNMATCHED_ROUTE = "(unmatched)";
|
|
7
|
+
export interface RouteStats {
|
|
8
|
+
/** The route pattern, so `/users/1` and `/users/2` share one series. */
|
|
9
|
+
readonly route: string;
|
|
10
|
+
readonly method: string;
|
|
11
|
+
readonly count: number;
|
|
12
|
+
/** Keyed by status code as a string, because that is what JSON gives back. */
|
|
13
|
+
readonly byStatus: Readonly<Record<string, number>>;
|
|
14
|
+
/** Nanoseconds. */
|
|
15
|
+
readonly duration: HistogramSnapshot;
|
|
16
|
+
/**
|
|
17
|
+
* The trace of the slowest request on this route so far, which is the only
|
|
18
|
+
* question a p99 provokes: which request was it, and where are its logs.
|
|
19
|
+
*/
|
|
20
|
+
readonly slowestTraceId?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface HttpStatsReport {
|
|
23
|
+
readonly routes: readonly RouteStats[];
|
|
24
|
+
/** Read off `Bun.serve` at 14.7 ns rather than counted, so dunx counts nothing. */
|
|
25
|
+
readonly inFlight: number;
|
|
26
|
+
readonly pendingWebSockets: number;
|
|
27
|
+
/** When the counters were last reset, or boot. */
|
|
28
|
+
readonly since: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One series per route, keyed on the frozen `RouteContext` that `buildContext`
|
|
32
|
+
* makes once at boot. That object identity is the label set: a `Map` lookup on it
|
|
33
|
+
* is 8.8 ns, where building `${method} ${path}` and hashing it is 206.6 ns.
|
|
34
|
+
*
|
|
35
|
+
* Series count is bounded by the handler count, because `ctx.path` is the route
|
|
36
|
+
* pattern rather than the request's path.
|
|
37
|
+
*
|
|
38
|
+
* Bound by `HttpFactory`'s global wrapper, like `PubSub` and `ClientAddress`: an
|
|
39
|
+
* unbound class self-binds into whichever scope asks first, so a second consumer
|
|
40
|
+
* would be a boot error.
|
|
41
|
+
*
|
|
42
|
+
* `observe` takes everything as parameters and reads no ambient store, which is
|
|
43
|
+
* what keeps it at 35.2 ns folded into the `.then` request logging already
|
|
44
|
+
* allocates.
|
|
45
|
+
*/
|
|
46
|
+
export declare class RequestMetrics {
|
|
47
|
+
#private;
|
|
48
|
+
observe(ctx: RouteContext, status: number, durationNs: number, traceId?: string): void;
|
|
49
|
+
snapshot(): HttpStatsReport;
|
|
50
|
+
/**
|
|
51
|
+
* Drops every series rather than zeroing them, so a route that stopped being
|
|
52
|
+
* called stops being reported. A cumulative histogram over a week has a p99
|
|
53
|
+
* reflecting a deploy three days ago; who calls this is the app's decision.
|
|
54
|
+
*/
|
|
55
|
+
reset(): void;
|
|
56
|
+
/** Internal: `listen()` hands the bound server to the resolved singleton. */
|
|
57
|
+
attach(server: Server<unknown>): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether `MetricsMiddleware` is the thing doing the observing.
|
|
61
|
+
*
|
|
62
|
+
* With request logging on - the default - `RequestLoggingMiddleware` observes
|
|
63
|
+
* from the `.then` it already allocates and this middleware would double-count.
|
|
64
|
+
* Exported because `HttpFactory` binds it and `HttpApplication` installs it, and
|
|
65
|
+
* the two disagreeing would mean either no metrics or twice as many.
|
|
66
|
+
*/
|
|
67
|
+
export declare const usesMetricsMiddleware: (options: {
|
|
68
|
+
readonly metrics?: boolean;
|
|
69
|
+
readonly requestLogging?: unknown;
|
|
70
|
+
}) => boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Installed by `HttpFactory` only when `requestLogging: false`. With logging on -
|
|
73
|
+
* the default - `RequestLoggingMiddleware` calls `observe` from the `.then` it
|
|
74
|
+
* already allocates, at 35.2 ns against this middleware's 175.9 ns standalone.
|
|
75
|
+
*/
|
|
76
|
+
export declare class MetricsMiddleware implements Middleware {
|
|
77
|
+
private readonly metrics;
|
|
78
|
+
constructor(metrics: RequestMetrics);
|
|
79
|
+
handle(_req: BunRequest, ctx: RouteContext, next: Next): Promise<Response>;
|
|
80
|
+
}
|
|
@@ -75,6 +75,8 @@ export declare abstract class HttpOptionsProvider {
|
|
|
75
75
|
/** `false` removes the middleware from the chain; an object tunes it. */
|
|
76
76
|
get requestLogging(): boolean | RequestLoggingOptions;
|
|
77
77
|
get socketLogging(): boolean | SocketLoggingOptions;
|
|
78
|
+
/** Per-route counts and timings. Off by default; see {@link RequestMetrics}. */
|
|
79
|
+
get metrics(): boolean;
|
|
78
80
|
/**
|
|
79
81
|
* Replaces the default mapper. Prefer an `ErrorFilter` class over a bare
|
|
80
82
|
* function: a class is resolved from the container and can inject.
|
|
@@ -2,6 +2,7 @@ import { Logger, RequestContext } from '@dunx/core';
|
|
|
2
2
|
import type { BunRequest } from 'bun';
|
|
3
3
|
import type { RouteContext } from './context.js';
|
|
4
4
|
import type { Middleware, Next } from './middleware.js';
|
|
5
|
+
import type { RequestMetrics } from './metrics.js';
|
|
5
6
|
export interface RequestLoggingOptions {
|
|
6
7
|
/** Bodies past this many characters are logged as a size. Default 2048. `0` omits them. */
|
|
7
8
|
readonly maxBodyLength?: number;
|
|
@@ -17,7 +18,7 @@ export interface RequestLoggingOptions {
|
|
|
17
18
|
* materialised string by the time this clones it. */
|
|
18
19
|
readonly responseBody?: boolean;
|
|
19
20
|
/**
|
|
20
|
-
* Paths to skip entirely: no entry, no `
|
|
21
|
+
* Paths to skip entirely: no entry, no trace, no `traceresponse`, and no
|
|
21
22
|
* `AsyncLocalStorage` scope, so anything the handler logs is uncorrelated.
|
|
22
23
|
* `correlateIgnored` buys the correlation back.
|
|
23
24
|
*/
|
|
@@ -33,24 +34,37 @@ export interface RequestLoggingOptions {
|
|
|
33
34
|
*/
|
|
34
35
|
readonly ignorePrefix?: readonly string[];
|
|
35
36
|
/**
|
|
36
|
-
* Keep the
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* Keep the trace and the async scope on an `ignore`d path. Default `false`.
|
|
38
|
+
* The path still writes no entry; it gets a `traceresponse` and everything the
|
|
39
|
+
* handler logs carries the trace. Costs ~2.2 us of the ~5.4 us the default path
|
|
40
|
+
* spends.
|
|
40
41
|
*/
|
|
41
42
|
readonly correlateIgnored?: boolean;
|
|
42
43
|
/**
|
|
43
44
|
* Wrap every request in an `AsyncLocalStorage` scope. Default `true`, +0.91 us.
|
|
44
|
-
* It is what lets a service four frames down log `
|
|
45
|
+
* It is what lets a service four frames down log `traceId` without being
|
|
45
46
|
* handed a request. `correlate: false` skips it; this middleware's own entry is
|
|
46
|
-
* unchanged, but every other line the request writes loses its
|
|
47
|
+
* unchanged, but every other line the request writes loses its trace.
|
|
47
48
|
*/
|
|
48
49
|
readonly correlate?: boolean;
|
|
49
50
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* Put `traceresponse` on the response. Default `true`, and ~500 ns of the 4.7 us
|
|
52
|
+
* the path costs, which is the largest thing here that can go without losing a
|
|
53
|
+
* field from a line.
|
|
54
|
+
*
|
|
55
|
+
* `false` keeps the trace on this middleware's own lines, in the async scope and
|
|
56
|
+
* on the metrics exemplar, and withholds the header from every response
|
|
57
|
+
* including a failure's: the error mapper stamps from what `TraceContext.adopt`
|
|
58
|
+
* marked, and this stops it marking.
|
|
59
|
+
*/
|
|
60
|
+
readonly traceResponse?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Adopt W3C Trace Context, putting `traceId`, `spanId`, `parentSpanId` and
|
|
63
|
+
* `traceFlags` on every line the request writes and `traceresponse` on its
|
|
64
|
+
* response. Default `true`, at 49.2 ns to mint both ids plus one header read.
|
|
65
|
+
* `@dunx/http/client` sends the adopted trace upstream.
|
|
66
|
+
*
|
|
67
|
+
* `false` removes it, and a request then carries no correlation id at all.
|
|
54
68
|
*/
|
|
55
69
|
readonly trace?: boolean;
|
|
56
70
|
}
|
|
@@ -70,6 +84,6 @@ export declare class RequestLoggingMiddleware implements Middleware {
|
|
|
70
84
|
#private;
|
|
71
85
|
private readonly logger;
|
|
72
86
|
private readonly context;
|
|
73
|
-
constructor(logger: Logger, context: RequestContext, options?: RequestLoggingOptions);
|
|
87
|
+
constructor(logger: Logger, context: RequestContext, options?: RequestLoggingOptions, metrics?: RequestMetrics);
|
|
74
88
|
handle(req: BunRequest, ctx: RouteContext, next: Next): Promise<Response>;
|
|
75
89
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export declare const TRACEPARENT_HEADER = "traceparent";
|
|
2
2
|
export declare const TRACESTATE_HEADER = "tracestate";
|
|
3
|
+
/**
|
|
4
|
+
* The span that answered, sent back so a caller can record which of the callee's
|
|
5
|
+
* spans its own span points at. Same four fields as `traceparent`, and the
|
|
6
|
+
* the one correlation id a response carries.
|
|
7
|
+
*
|
|
8
|
+
* A W3C Distributed Tracing Working Group proposal rather than a ratified
|
|
9
|
+
* standard: `traceparent` and `tracestate` are the Recommendation, and the
|
|
10
|
+
* published Trace Context Level 2 Candidate Recommendation Draft covers those two
|
|
11
|
+
* request headers and not this response one. The format is specified and stable,
|
|
12
|
+
* and adoption is thin, so treat a caller reading it as a bonus.
|
|
13
|
+
*/
|
|
14
|
+
export declare const TRACERESPONSE_HEADER = "traceresponse";
|
|
3
15
|
export interface Trace {
|
|
4
16
|
/** 32 hex digits, shared by every span in the trace. */
|
|
5
17
|
readonly traceId: string;
|
|
@@ -15,14 +27,18 @@ export interface Trace {
|
|
|
15
27
|
/**
|
|
16
28
|
* W3C Trace Context, propagated across services.
|
|
17
29
|
*
|
|
18
|
-
* The whole of it is one header parsed and
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
30
|
+
* The whole of it is one header parsed and two written. There is no exporter, no
|
|
31
|
+
* sampler and no dependency: every log line a request writes carries the same
|
|
32
|
+
* `traceId` the service upstream logged, so the two join without either of them
|
|
33
|
+
* running a collector.
|
|
34
|
+
*
|
|
35
|
+
* `traceId`, `spanId` and `parentSpanId` are the OpenTelemetry log data model's
|
|
36
|
+
* own fields, so a collector that ingests these lines correlates them with spans
|
|
37
|
+
* emitted by anything else speaking the standard. Bun 1.4.0 runs OpenTelemetry's
|
|
38
|
+
* Node instrumentation, and a trace adopted here is the trace those spans join.
|
|
22
39
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* which is not worth spending in a service that has nothing to correlate with.
|
|
40
|
+
* On by default. `requestLogging: { trace: false }` removes it, at which point a
|
|
41
|
+
* request carries no correlation id at all.
|
|
26
42
|
*/
|
|
27
43
|
export declare class TraceContext {
|
|
28
44
|
#private;
|
|
@@ -32,10 +48,15 @@ export declare class TraceContext {
|
|
|
32
48
|
* higher version keeps its first four fields, so a future format still
|
|
33
49
|
* propagates.
|
|
34
50
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
51
|
+
* A trace that arrived is continued with a span of this server's own, and the
|
|
52
|
+
* caller's sampling decision is kept rather than overridden.
|
|
53
|
+
*
|
|
54
|
+
* `expose: false` adopts the trace without marking it for {@link stamp}, so no
|
|
55
|
+
* `traceresponse` is written - by this middleware or by the error mapper, which
|
|
56
|
+
* builds its own `Response` from what was recorded here. Everything inward is
|
|
57
|
+
* unchanged: the scope, the log lines, the metrics exemplar.
|
|
37
58
|
*/
|
|
38
|
-
static adopt(req: Request,
|
|
59
|
+
static adopt(req: Request, expose?: boolean): Trace;
|
|
39
60
|
/** The trace adopted for this request, if one was. */
|
|
40
61
|
static of(req: Request): Trace | undefined;
|
|
41
62
|
/**
|
|
@@ -43,5 +64,15 @@ export declare class TraceContext {
|
|
|
43
64
|
* parent, so the two link without inventing a span nothing logged.
|
|
44
65
|
*/
|
|
45
66
|
static header(trace: Pick<Trace, 'traceId' | 'spanId' | 'flags'>): string;
|
|
67
|
+
/**
|
|
68
|
+
* The response, carrying `traceresponse` if this request adopted a trace.
|
|
69
|
+
*
|
|
70
|
+
* The logging middleware sets the header on a response it returns, and a
|
|
71
|
+
* failure is never one: the error mapper builds a fresh `Response` outside the
|
|
72
|
+
* chain, so a guard's 401, a validation 400 and every unmatched 404 would go out
|
|
73
|
+
* bare. Read back from the request rather than threaded through the mapper,
|
|
74
|
+
* which an app writes its own of.
|
|
75
|
+
*/
|
|
76
|
+
static stamp(response: Response, req: Request): Response;
|
|
46
77
|
static sampled(trace: Pick<Trace, 'flags'>): boolean;
|
|
47
78
|
}
|
package/dist/ws/decorators.d.ts
CHANGED
|
@@ -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;
|
package/dist/ws/relay.d.ts
CHANGED
|
@@ -27,6 +27,14 @@ export interface PubSubRelay {
|
|
|
27
27
|
*/
|
|
28
28
|
close?(): unknown;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Both relays take a URL, and a wrong scheme has to fail here rather than at
|
|
32
|
+
* connect time: an absence-tolerant relay swallows the connection error, so a
|
|
33
|
+
* typo would degrade silently to single-node fan-out.
|
|
34
|
+
*/
|
|
35
|
+
export declare const assertRelayUrl: (url: string, protocols: readonly string[], example: string) => string;
|
|
36
|
+
/** The URL with any password removed, for logs and error messages. */
|
|
37
|
+
export declare const redactUrl: (url: string) => string;
|
|
30
38
|
/** Which relay call failed, so one message can say what degraded. */
|
|
31
39
|
export type RelayPhase = 'publish' | 'subscribe' | 'close';
|
|
32
40
|
export interface RelayOptions {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dunx/http",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "Bun.serve adapter for the dunx framework: controllers, middleware and WebSocket gateways",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -56,13 +56,16 @@
|
|
|
56
56
|
"typecheck": "tsc --noEmit"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@arkv/shared": "
|
|
59
|
+
"@arkv/shared": "0.8.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@dunx/core": "workspace:*"
|
|
62
|
+
"@dunx/core": "workspace:*",
|
|
63
|
+
"@opentelemetry/api": "1.9.1",
|
|
64
|
+
"@opentelemetry/core": "2.11.0",
|
|
65
|
+
"@opentelemetry/sdk-trace-node": "2.11.0"
|
|
63
66
|
},
|
|
64
67
|
"peerDependencies": {
|
|
65
|
-
"@dunx/core": "^3.1
|
|
68
|
+
"@dunx/core": "^3.2.1",
|
|
66
69
|
"@types/bun": ">=1.3.0"
|
|
67
70
|
},
|
|
68
71
|
"peerDependenciesMeta": {
|