@bymax-one/nest-core 1.6.5 → 1.6.7
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 +53 -1
- package/README.md +113 -24
- package/dist/http/index.cjs +64 -0
- package/dist/http/index.d.cts +59 -0
- package/dist/http/index.d.ts +59 -0
- package/dist/http/index.mjs +62 -0
- package/dist/index.cjs +137 -72
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/index.mjs +139 -74
- package/package.json +25 -3
package/dist/index.cjs
CHANGED
|
@@ -18,6 +18,114 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
18
18
|
};
|
|
19
19
|
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
20
20
|
|
|
21
|
+
// src/core.tokens.ts
|
|
22
|
+
var BYMAX_CORE_OPTIONS = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:core-options");
|
|
23
|
+
var BYMAX_CORRELATION_PROVIDER = /* @__PURE__ */ Symbol.for(
|
|
24
|
+
"@bymax-one/nest-core:correlation-provider"
|
|
25
|
+
);
|
|
26
|
+
var BYMAX_TIMING_SINK = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:timing-sink");
|
|
27
|
+
var BYMAX_HEALTH_INDICATORS = /* @__PURE__ */ Symbol.for(
|
|
28
|
+
"@bymax-one/nest-core:health-indicators"
|
|
29
|
+
);
|
|
30
|
+
var BYMAX_HEALTH_TRANSITION_SINK = /* @__PURE__ */ Symbol.for(
|
|
31
|
+
"@bymax-one/nest-core:health-transition-sink"
|
|
32
|
+
);
|
|
33
|
+
var BYMAX_UNEXPECTED_ERROR_SINK = /* @__PURE__ */ Symbol.for(
|
|
34
|
+
"@bymax-one/nest-core:unexpected-error-sink"
|
|
35
|
+
);
|
|
36
|
+
var BYMAX_METRICS_REGISTRY = /* @__PURE__ */ Symbol.for(
|
|
37
|
+
"@bymax-one/nest-core:metrics-registry"
|
|
38
|
+
);
|
|
39
|
+
var BYMAX_TRACE_CONTEXT = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:trace-context");
|
|
40
|
+
|
|
41
|
+
// src/envelope/error-codes.ts
|
|
42
|
+
var BYMAX_BAD_REQUEST = "BYMAX_BAD_REQUEST";
|
|
43
|
+
var BYMAX_VALIDATION_FAILED = "BYMAX_VALIDATION_FAILED";
|
|
44
|
+
var BYMAX_UNAUTHORIZED = "BYMAX_UNAUTHORIZED";
|
|
45
|
+
var BYMAX_FORBIDDEN = "BYMAX_FORBIDDEN";
|
|
46
|
+
var BYMAX_NOT_FOUND = "BYMAX_NOT_FOUND";
|
|
47
|
+
var BYMAX_CONFLICT = "BYMAX_CONFLICT";
|
|
48
|
+
var BYMAX_PAYLOAD_TOO_LARGE = "BYMAX_PAYLOAD_TOO_LARGE";
|
|
49
|
+
var BYMAX_UNSUPPORTED_MEDIA_TYPE = "BYMAX_UNSUPPORTED_MEDIA_TYPE";
|
|
50
|
+
var BYMAX_UNPROCESSABLE_ENTITY = "BYMAX_UNPROCESSABLE_ENTITY";
|
|
51
|
+
var BYMAX_TOO_MANY_REQUESTS = "BYMAX_TOO_MANY_REQUESTS";
|
|
52
|
+
var BYMAX_CLIENT_ERROR = "BYMAX_CLIENT_ERROR";
|
|
53
|
+
var BYMAX_INTERNAL_ERROR = "BYMAX_INTERNAL_ERROR";
|
|
54
|
+
var BYMAX_NOT_IMPLEMENTED = "BYMAX_NOT_IMPLEMENTED";
|
|
55
|
+
var BYMAX_BAD_GATEWAY = "BYMAX_BAD_GATEWAY";
|
|
56
|
+
var BYMAX_SERVICE_UNAVAILABLE = "BYMAX_SERVICE_UNAVAILABLE";
|
|
57
|
+
var BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
|
|
58
|
+
var STATUS_CODES = /* @__PURE__ */ new Map([
|
|
59
|
+
[400, BYMAX_BAD_REQUEST],
|
|
60
|
+
[401, BYMAX_UNAUTHORIZED],
|
|
61
|
+
[403, BYMAX_FORBIDDEN],
|
|
62
|
+
[404, BYMAX_NOT_FOUND],
|
|
63
|
+
[409, BYMAX_CONFLICT],
|
|
64
|
+
[413, BYMAX_PAYLOAD_TOO_LARGE],
|
|
65
|
+
[415, BYMAX_UNSUPPORTED_MEDIA_TYPE],
|
|
66
|
+
[422, BYMAX_UNPROCESSABLE_ENTITY],
|
|
67
|
+
[429, BYMAX_TOO_MANY_REQUESTS],
|
|
68
|
+
[500, BYMAX_INTERNAL_ERROR],
|
|
69
|
+
[501, BYMAX_NOT_IMPLEMENTED],
|
|
70
|
+
[502, BYMAX_BAD_GATEWAY],
|
|
71
|
+
[503, BYMAX_SERVICE_UNAVAILABLE],
|
|
72
|
+
[504, BYMAX_GATEWAY_TIMEOUT]
|
|
73
|
+
]);
|
|
74
|
+
var CLIENT_ERROR_MIN = 400;
|
|
75
|
+
var CLIENT_ERROR_MAX = 500;
|
|
76
|
+
function codeForStatus(status) {
|
|
77
|
+
const catalogued = STATUS_CODES.get(status);
|
|
78
|
+
if (catalogued !== void 0) {
|
|
79
|
+
return catalogued;
|
|
80
|
+
}
|
|
81
|
+
if (status >= CLIENT_ERROR_MIN && status < CLIENT_ERROR_MAX) {
|
|
82
|
+
return BYMAX_CLIENT_ERROR;
|
|
83
|
+
}
|
|
84
|
+
return BYMAX_INTERNAL_ERROR;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/body-depth/body-depth.middleware.ts
|
|
88
|
+
var BODY_TOO_DEEP_MESSAGE = "Request body is nested too deeply";
|
|
89
|
+
function exceedsDepth(value, maxDepth) {
|
|
90
|
+
let level = [value];
|
|
91
|
+
for (let depth = 0; level.length > 0; depth += 1) {
|
|
92
|
+
const next = [];
|
|
93
|
+
for (const node of level) {
|
|
94
|
+
if (typeof node === "object" && node !== null) {
|
|
95
|
+
if (depth >= maxDepth) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
for (const child of Object.values(node)) {
|
|
99
|
+
next.push(child);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
level = next;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
exports.BymaxBodyDepthMiddleware = class BymaxBodyDepthMiddleware {
|
|
108
|
+
constructor(options) {
|
|
109
|
+
this.options = options;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* @param req - The request, whose `body` the parser has already filled.
|
|
113
|
+
* @param _res - Unused.
|
|
114
|
+
* @param next - Continues the chain.
|
|
115
|
+
* @throws BadRequestException When the body is nested too deeply.
|
|
116
|
+
*/
|
|
117
|
+
use(req, _res, next) {
|
|
118
|
+
if (exceedsDepth(req.body, this.options.bodyDepth.maxDepth)) {
|
|
119
|
+
throw new common.BadRequestException({ code: BYMAX_BAD_REQUEST, message: BODY_TOO_DEEP_MESSAGE });
|
|
120
|
+
}
|
|
121
|
+
next();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
exports.BymaxBodyDepthMiddleware = __decorateClass([
|
|
125
|
+
common.Injectable(),
|
|
126
|
+
__decorateParam(0, common.Inject(BYMAX_CORE_OPTIONS))
|
|
127
|
+
], exports.BymaxBodyDepthMiddleware);
|
|
128
|
+
|
|
21
129
|
// src/route-defaults.ts
|
|
22
130
|
var DEFAULT_HEALTH_PATH = "health";
|
|
23
131
|
var DEFAULT_METRICS_PATH = "metrics";
|
|
@@ -64,6 +172,16 @@ function resolveTiming(raw) {
|
|
|
64
172
|
const threshold = raw?.slowRequestThresholdMs;
|
|
65
173
|
return threshold === void 0 ? { enabled } : { enabled, slowRequestThresholdMs: threshold };
|
|
66
174
|
}
|
|
175
|
+
var DEFAULT_MAX_BODY_DEPTH = 32;
|
|
176
|
+
function resolveBodyDepth(raw) {
|
|
177
|
+
const maxDepth = raw?.maxDepth ?? DEFAULT_MAX_BODY_DEPTH;
|
|
178
|
+
if (!Number.isSafeInteger(maxDepth) || maxDepth < 1) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`bodyDepth.maxDepth must be a positive safe integer, received ${String(maxDepth)}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return { enabled: raw?.enabled ?? false, maxDepth };
|
|
184
|
+
}
|
|
67
185
|
function resolveHealth(raw) {
|
|
68
186
|
return {
|
|
69
187
|
enabled: raw?.enabled ?? true,
|
|
@@ -136,6 +254,7 @@ function normalizeCoreOptions(raw) {
|
|
|
136
254
|
return deepFreeze({
|
|
137
255
|
envelope: resolveEnvelope(raw?.envelope),
|
|
138
256
|
timing: resolveTiming(raw?.timing),
|
|
257
|
+
bodyDepth: resolveBodyDepth(raw?.bodyDepth),
|
|
139
258
|
health: resolveHealth(raw?.health),
|
|
140
259
|
metrics: resolveMetrics(raw?.metrics),
|
|
141
260
|
openapi: resolveOpenApi(raw?.openapi, raw?.environment),
|
|
@@ -148,26 +267,6 @@ function normalizeCoreOptions(raw) {
|
|
|
148
267
|
}
|
|
149
268
|
normalizeCoreOptions();
|
|
150
269
|
|
|
151
|
-
// src/core.tokens.ts
|
|
152
|
-
var BYMAX_CORE_OPTIONS = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:core-options");
|
|
153
|
-
var BYMAX_CORRELATION_PROVIDER = /* @__PURE__ */ Symbol.for(
|
|
154
|
-
"@bymax-one/nest-core:correlation-provider"
|
|
155
|
-
);
|
|
156
|
-
var BYMAX_TIMING_SINK = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:timing-sink");
|
|
157
|
-
var BYMAX_HEALTH_INDICATORS = /* @__PURE__ */ Symbol.for(
|
|
158
|
-
"@bymax-one/nest-core:health-indicators"
|
|
159
|
-
);
|
|
160
|
-
var BYMAX_HEALTH_TRANSITION_SINK = /* @__PURE__ */ Symbol.for(
|
|
161
|
-
"@bymax-one/nest-core:health-transition-sink"
|
|
162
|
-
);
|
|
163
|
-
var BYMAX_UNEXPECTED_ERROR_SINK = /* @__PURE__ */ Symbol.for(
|
|
164
|
-
"@bymax-one/nest-core:unexpected-error-sink"
|
|
165
|
-
);
|
|
166
|
-
var BYMAX_METRICS_REGISTRY = /* @__PURE__ */ Symbol.for(
|
|
167
|
-
"@bymax-one/nest-core:metrics-registry"
|
|
168
|
-
);
|
|
169
|
-
var BYMAX_TRACE_CONTEXT = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:trace-context");
|
|
170
|
-
|
|
171
270
|
// src/optional-peer.ts
|
|
172
271
|
function isMissingModuleError(cause) {
|
|
173
272
|
const code = cause.code;
|
|
@@ -275,52 +374,6 @@ function containRejection(returned, onFailure) {
|
|
|
275
374
|
}
|
|
276
375
|
}
|
|
277
376
|
|
|
278
|
-
// src/envelope/error-codes.ts
|
|
279
|
-
var BYMAX_BAD_REQUEST = "BYMAX_BAD_REQUEST";
|
|
280
|
-
var BYMAX_VALIDATION_FAILED = "BYMAX_VALIDATION_FAILED";
|
|
281
|
-
var BYMAX_UNAUTHORIZED = "BYMAX_UNAUTHORIZED";
|
|
282
|
-
var BYMAX_FORBIDDEN = "BYMAX_FORBIDDEN";
|
|
283
|
-
var BYMAX_NOT_FOUND = "BYMAX_NOT_FOUND";
|
|
284
|
-
var BYMAX_CONFLICT = "BYMAX_CONFLICT";
|
|
285
|
-
var BYMAX_PAYLOAD_TOO_LARGE = "BYMAX_PAYLOAD_TOO_LARGE";
|
|
286
|
-
var BYMAX_UNSUPPORTED_MEDIA_TYPE = "BYMAX_UNSUPPORTED_MEDIA_TYPE";
|
|
287
|
-
var BYMAX_UNPROCESSABLE_ENTITY = "BYMAX_UNPROCESSABLE_ENTITY";
|
|
288
|
-
var BYMAX_TOO_MANY_REQUESTS = "BYMAX_TOO_MANY_REQUESTS";
|
|
289
|
-
var BYMAX_CLIENT_ERROR = "BYMAX_CLIENT_ERROR";
|
|
290
|
-
var BYMAX_INTERNAL_ERROR = "BYMAX_INTERNAL_ERROR";
|
|
291
|
-
var BYMAX_NOT_IMPLEMENTED = "BYMAX_NOT_IMPLEMENTED";
|
|
292
|
-
var BYMAX_BAD_GATEWAY = "BYMAX_BAD_GATEWAY";
|
|
293
|
-
var BYMAX_SERVICE_UNAVAILABLE = "BYMAX_SERVICE_UNAVAILABLE";
|
|
294
|
-
var BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
|
|
295
|
-
var STATUS_CODES = /* @__PURE__ */ new Map([
|
|
296
|
-
[400, BYMAX_BAD_REQUEST],
|
|
297
|
-
[401, BYMAX_UNAUTHORIZED],
|
|
298
|
-
[403, BYMAX_FORBIDDEN],
|
|
299
|
-
[404, BYMAX_NOT_FOUND],
|
|
300
|
-
[409, BYMAX_CONFLICT],
|
|
301
|
-
[413, BYMAX_PAYLOAD_TOO_LARGE],
|
|
302
|
-
[415, BYMAX_UNSUPPORTED_MEDIA_TYPE],
|
|
303
|
-
[422, BYMAX_UNPROCESSABLE_ENTITY],
|
|
304
|
-
[429, BYMAX_TOO_MANY_REQUESTS],
|
|
305
|
-
[500, BYMAX_INTERNAL_ERROR],
|
|
306
|
-
[501, BYMAX_NOT_IMPLEMENTED],
|
|
307
|
-
[502, BYMAX_BAD_GATEWAY],
|
|
308
|
-
[503, BYMAX_SERVICE_UNAVAILABLE],
|
|
309
|
-
[504, BYMAX_GATEWAY_TIMEOUT]
|
|
310
|
-
]);
|
|
311
|
-
var CLIENT_ERROR_MIN = 400;
|
|
312
|
-
var CLIENT_ERROR_MAX = 500;
|
|
313
|
-
function codeForStatus(status) {
|
|
314
|
-
const catalogued = STATUS_CODES.get(status);
|
|
315
|
-
if (catalogued !== void 0) {
|
|
316
|
-
return catalogued;
|
|
317
|
-
}
|
|
318
|
-
if (status >= CLIENT_ERROR_MIN && status < CLIENT_ERROR_MAX) {
|
|
319
|
-
return BYMAX_CLIENT_ERROR;
|
|
320
|
-
}
|
|
321
|
-
return BYMAX_INTERNAL_ERROR;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
377
|
// src/envelope/error-envelope.ts
|
|
325
378
|
function buildErrorEnvelope(input) {
|
|
326
379
|
const base = {
|
|
@@ -359,6 +412,12 @@ function extractRequestInfo(context) {
|
|
|
359
412
|
var INTERNAL_ERROR_STATUS = 500;
|
|
360
413
|
var INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
361
414
|
var VALIDATION_FAILED_MESSAGE = "Validation failed";
|
|
415
|
+
var MALFORMED_REQUEST_MESSAGE = "Malformed request";
|
|
416
|
+
var PRE_ROUTING_MESSAGES = /* @__PURE__ */ new Map([
|
|
417
|
+
[400, MALFORMED_REQUEST_MESSAGE],
|
|
418
|
+
[413, "Request entity too large"],
|
|
419
|
+
[415, "Unsupported media type"]
|
|
420
|
+
]);
|
|
362
421
|
var ABSOLUTE_FORM_PREFIX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
|
|
363
422
|
function pathOf(url) {
|
|
364
423
|
let target = String(url);
|
|
@@ -409,8 +468,9 @@ function resolveExposedClientError(exception) {
|
|
|
409
468
|
if (typeof status !== "number" || !Number.isInteger(status) || status < CLIENT_ERROR_MIN2 || status >= CLIENT_ERROR_MAX2) {
|
|
410
469
|
return void 0;
|
|
411
470
|
}
|
|
471
|
+
const fixed = typeof candidate.type === "string" ? PRE_ROUTING_MESSAGES.get(status) : void 0;
|
|
412
472
|
const message = exception instanceof Error ? exception.message : "";
|
|
413
|
-
return { status, message: message === "" ? INTERNAL_ERROR_MESSAGE : message };
|
|
473
|
+
return { status, message: fixed ?? (message === "" ? INTERNAL_ERROR_MESSAGE : message) };
|
|
414
474
|
}
|
|
415
475
|
function isValidationResponse(response) {
|
|
416
476
|
return typeof response === "object" && response !== null && "message" in response && Array.isArray(response.message);
|
|
@@ -611,6 +671,9 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
|
|
|
611
671
|
toValidationDetails(response.message)
|
|
612
672
|
);
|
|
613
673
|
}
|
|
674
|
+
if (status === 400 && readRequestInfo(request).route === UNMATCHED_ROUTE) {
|
|
675
|
+
return this.toEnvelope(status, codeForStatus(status), MALFORMED_REQUEST_MESSAGE, context);
|
|
676
|
+
}
|
|
614
677
|
const message = extractHttpMessage(response, exception);
|
|
615
678
|
const rawUrl = String(this.adapterHost.httpAdapter.getRequestUrl(request));
|
|
616
679
|
return this.toEnvelope(
|
|
@@ -1687,12 +1750,14 @@ exports.BymaxCoreModule = class BymaxCoreModule extends BymaxCoreModuleBase {
|
|
|
1687
1750
|
* @param consumer - Nest's middleware consumer.
|
|
1688
1751
|
*/
|
|
1689
1752
|
configure(consumer) {
|
|
1690
|
-
if (!this.resolved.timing.enabled) {
|
|
1691
|
-
return;
|
|
1692
|
-
}
|
|
1693
1753
|
const adapter = this.adapterHost?.httpAdapter;
|
|
1694
|
-
|
|
1695
|
-
|
|
1754
|
+
if (this.resolved.timing.enabled) {
|
|
1755
|
+
const onFastify = bridgeFastifyRouteMetadata(adapter);
|
|
1756
|
+
consumer.apply(exports.BymaxTimingMiddleware).forRoutes(onFastify ? "{*splat}" : "/");
|
|
1757
|
+
}
|
|
1758
|
+
if (this.resolved.bodyDepth.enabled && adapter?.getType?.() !== "fastify") {
|
|
1759
|
+
consumer.apply(exports.BymaxBodyDepthMiddleware).forRoutes("/");
|
|
1760
|
+
}
|
|
1696
1761
|
}
|
|
1697
1762
|
/**
|
|
1698
1763
|
* Register the module synchronously. Options are known now, so disabled
|
package/dist/index.d.cts
CHANGED
|
@@ -22,6 +22,25 @@ interface TimingOptions {
|
|
|
22
22
|
/** Samples above this threshold are flagged as slow. No default (unset). */
|
|
23
23
|
slowRequestThresholdMs?: number;
|
|
24
24
|
}
|
|
25
|
+
/** Request-body nesting limit, applied before the application's own validation runs. */
|
|
26
|
+
interface BodyDepthOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Refuse a parsed request body nested deeper than {@link BodyDepthOptions.maxDepth} with a
|
|
29
|
+
* `400` through the error envelope. Default: `false`.
|
|
30
|
+
*
|
|
31
|
+
* Off by default because turning it on rejects bodies that are accepted today. Enable it:
|
|
32
|
+
* a body of a few thousand nested arrays is only a few kilobytes, and a recursive validator
|
|
33
|
+
* or transformer overflows the stack on it, so the request is answered `500`, with no
|
|
34
|
+
* credential needed. The check runs before routing, on the body the parser already produced, so it covers the Express adapter and a body parsed by
|
|
35
|
+
* Nest's own parsers; it does not cover Fastify.
|
|
36
|
+
*/
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The deepest nesting a body may have: `[[1]]` is depth 2, a flat object or array is depth 1,
|
|
40
|
+
* and a scalar is depth 0. Must be a positive safe integer. Default: `32`.
|
|
41
|
+
*/
|
|
42
|
+
maxDepth?: number;
|
|
43
|
+
}
|
|
25
44
|
/** Liveness and readiness endpoint configuration. */
|
|
26
45
|
interface HealthOptions {
|
|
27
46
|
/** Register the health controller. Default: `true`. */
|
|
@@ -267,6 +286,8 @@ interface BymaxCoreModuleOptions {
|
|
|
267
286
|
envelope?: EnvelopeOptions;
|
|
268
287
|
/** Request-timing middleware. Default: enabled. */
|
|
269
288
|
timing?: TimingOptions;
|
|
289
|
+
/** Request-body nesting limit. Default: disabled. */
|
|
290
|
+
bodyDepth?: BodyDepthOptions;
|
|
270
291
|
/** Liveness and readiness endpoints. Default: enabled. */
|
|
271
292
|
health?: HealthOptions;
|
|
272
293
|
/** Prometheus metrics endpoint. Default: disabled. */
|
|
@@ -308,6 +329,11 @@ interface ResolvedTimingOptions {
|
|
|
308
329
|
enabled: boolean;
|
|
309
330
|
slowRequestThresholdMs?: number;
|
|
310
331
|
}
|
|
332
|
+
/** Fully-resolved body-depth options. */
|
|
333
|
+
interface ResolvedBodyDepthOptions {
|
|
334
|
+
enabled: boolean;
|
|
335
|
+
maxDepth: number;
|
|
336
|
+
}
|
|
311
337
|
/** Fully-resolved health options. */
|
|
312
338
|
interface ResolvedHealthOptions {
|
|
313
339
|
enabled: boolean;
|
|
@@ -365,6 +391,7 @@ interface ResolvedOpenApiOptions {
|
|
|
365
391
|
interface ResolvedCoreOptions {
|
|
366
392
|
envelope: ResolvedEnvelopeOptions;
|
|
367
393
|
timing: ResolvedTimingOptions;
|
|
394
|
+
bodyDepth: ResolvedBodyDepthOptions;
|
|
368
395
|
health: ResolvedHealthOptions;
|
|
369
396
|
metrics: ResolvedMetricsOptions;
|
|
370
397
|
openapi: ResolvedOpenApiOptions;
|
|
@@ -1115,6 +1142,21 @@ declare class TimingInterceptor implements NestInterceptor {
|
|
|
1115
1142
|
private recordSample;
|
|
1116
1143
|
}
|
|
1117
1144
|
|
|
1145
|
+
/** Refuses a body nested deeper than `bodyDepth.maxDepth` with a coded `400`. */
|
|
1146
|
+
declare class BymaxBodyDepthMiddleware implements NestMiddleware {
|
|
1147
|
+
private readonly options;
|
|
1148
|
+
constructor(options: ResolvedCoreOptions);
|
|
1149
|
+
/**
|
|
1150
|
+
* @param req - The request, whose `body` the parser has already filled.
|
|
1151
|
+
* @param _res - Unused.
|
|
1152
|
+
* @param next - Continues the chain.
|
|
1153
|
+
* @throws BadRequestException When the body is nested too deeply.
|
|
1154
|
+
*/
|
|
1155
|
+
use(req: {
|
|
1156
|
+
body?: unknown;
|
|
1157
|
+
}, _res: unknown, next: () => void): void;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1118
1160
|
/**
|
|
1119
1161
|
* @fileoverview Neutral request-info accessor for request timing. Reads the
|
|
1120
1162
|
* HTTP method and the route template off a request without assuming Express or
|
|
@@ -1296,5 +1338,5 @@ declare const BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
|
|
|
1296
1338
|
*/
|
|
1297
1339
|
declare function codeForStatus(status: number): string;
|
|
1298
1340
|
|
|
1299
|
-
export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, BymaxCoreModule, BymaxExceptionFilter, BymaxTimingMiddleware, TimingInterceptor, UNMATCHED_ROUTE, buildErrorEnvelope, codeForStatus };
|
|
1300
|
-
export type { BuildErrorEnvelopeInput, BymaxCoreModuleOptions, EnvelopeOptions, ErrorDetails, ErrorEnvelope, FilterErrorContext, HealthOptions, ICorrelationIdProvider, ITimingSink, ITraceContextProvider, IUnexpectedErrorSink, MetricsOptions, OpenApiHttpMethod, OpenApiOperationIdFactory, OpenApiOperationKey, OpenApiOptions, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiServerDescriptor, OperationSecurityMap, RequestTimingSample, ResolvedCoreOptions, TelemetryOptions, TimingOptions, TraceContext, UnexpectedErrorReport };
|
|
1341
|
+
export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, BymaxBodyDepthMiddleware, BymaxCoreModule, BymaxExceptionFilter, BymaxTimingMiddleware, TimingInterceptor, UNMATCHED_ROUTE, buildErrorEnvelope, codeForStatus };
|
|
1342
|
+
export type { BodyDepthOptions, BuildErrorEnvelopeInput, BymaxCoreModuleOptions, EnvelopeOptions, ErrorDetails, ErrorEnvelope, FilterErrorContext, HealthOptions, ICorrelationIdProvider, ITimingSink, ITraceContextProvider, IUnexpectedErrorSink, MetricsOptions, OpenApiHttpMethod, OpenApiOperationIdFactory, OpenApiOperationKey, OpenApiOptions, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiServerDescriptor, OperationSecurityMap, RequestTimingSample, ResolvedCoreOptions, TelemetryOptions, TimingOptions, TraceContext, UnexpectedErrorReport };
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,25 @@ interface TimingOptions {
|
|
|
22
22
|
/** Samples above this threshold are flagged as slow. No default (unset). */
|
|
23
23
|
slowRequestThresholdMs?: number;
|
|
24
24
|
}
|
|
25
|
+
/** Request-body nesting limit, applied before the application's own validation runs. */
|
|
26
|
+
interface BodyDepthOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Refuse a parsed request body nested deeper than {@link BodyDepthOptions.maxDepth} with a
|
|
29
|
+
* `400` through the error envelope. Default: `false`.
|
|
30
|
+
*
|
|
31
|
+
* Off by default because turning it on rejects bodies that are accepted today. Enable it:
|
|
32
|
+
* a body of a few thousand nested arrays is only a few kilobytes, and a recursive validator
|
|
33
|
+
* or transformer overflows the stack on it, so the request is answered `500`, with no
|
|
34
|
+
* credential needed. The check runs before routing, on the body the parser already produced, so it covers the Express adapter and a body parsed by
|
|
35
|
+
* Nest's own parsers; it does not cover Fastify.
|
|
36
|
+
*/
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The deepest nesting a body may have: `[[1]]` is depth 2, a flat object or array is depth 1,
|
|
40
|
+
* and a scalar is depth 0. Must be a positive safe integer. Default: `32`.
|
|
41
|
+
*/
|
|
42
|
+
maxDepth?: number;
|
|
43
|
+
}
|
|
25
44
|
/** Liveness and readiness endpoint configuration. */
|
|
26
45
|
interface HealthOptions {
|
|
27
46
|
/** Register the health controller. Default: `true`. */
|
|
@@ -267,6 +286,8 @@ interface BymaxCoreModuleOptions {
|
|
|
267
286
|
envelope?: EnvelopeOptions;
|
|
268
287
|
/** Request-timing middleware. Default: enabled. */
|
|
269
288
|
timing?: TimingOptions;
|
|
289
|
+
/** Request-body nesting limit. Default: disabled. */
|
|
290
|
+
bodyDepth?: BodyDepthOptions;
|
|
270
291
|
/** Liveness and readiness endpoints. Default: enabled. */
|
|
271
292
|
health?: HealthOptions;
|
|
272
293
|
/** Prometheus metrics endpoint. Default: disabled. */
|
|
@@ -308,6 +329,11 @@ interface ResolvedTimingOptions {
|
|
|
308
329
|
enabled: boolean;
|
|
309
330
|
slowRequestThresholdMs?: number;
|
|
310
331
|
}
|
|
332
|
+
/** Fully-resolved body-depth options. */
|
|
333
|
+
interface ResolvedBodyDepthOptions {
|
|
334
|
+
enabled: boolean;
|
|
335
|
+
maxDepth: number;
|
|
336
|
+
}
|
|
311
337
|
/** Fully-resolved health options. */
|
|
312
338
|
interface ResolvedHealthOptions {
|
|
313
339
|
enabled: boolean;
|
|
@@ -365,6 +391,7 @@ interface ResolvedOpenApiOptions {
|
|
|
365
391
|
interface ResolvedCoreOptions {
|
|
366
392
|
envelope: ResolvedEnvelopeOptions;
|
|
367
393
|
timing: ResolvedTimingOptions;
|
|
394
|
+
bodyDepth: ResolvedBodyDepthOptions;
|
|
368
395
|
health: ResolvedHealthOptions;
|
|
369
396
|
metrics: ResolvedMetricsOptions;
|
|
370
397
|
openapi: ResolvedOpenApiOptions;
|
|
@@ -1115,6 +1142,21 @@ declare class TimingInterceptor implements NestInterceptor {
|
|
|
1115
1142
|
private recordSample;
|
|
1116
1143
|
}
|
|
1117
1144
|
|
|
1145
|
+
/** Refuses a body nested deeper than `bodyDepth.maxDepth` with a coded `400`. */
|
|
1146
|
+
declare class BymaxBodyDepthMiddleware implements NestMiddleware {
|
|
1147
|
+
private readonly options;
|
|
1148
|
+
constructor(options: ResolvedCoreOptions);
|
|
1149
|
+
/**
|
|
1150
|
+
* @param req - The request, whose `body` the parser has already filled.
|
|
1151
|
+
* @param _res - Unused.
|
|
1152
|
+
* @param next - Continues the chain.
|
|
1153
|
+
* @throws BadRequestException When the body is nested too deeply.
|
|
1154
|
+
*/
|
|
1155
|
+
use(req: {
|
|
1156
|
+
body?: unknown;
|
|
1157
|
+
}, _res: unknown, next: () => void): void;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1118
1160
|
/**
|
|
1119
1161
|
* @fileoverview Neutral request-info accessor for request timing. Reads the
|
|
1120
1162
|
* HTTP method and the route template off a request without assuming Express or
|
|
@@ -1296,5 +1338,5 @@ declare const BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
|
|
|
1296
1338
|
*/
|
|
1297
1339
|
declare function codeForStatus(status: number): string;
|
|
1298
1340
|
|
|
1299
|
-
export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, BymaxCoreModule, BymaxExceptionFilter, BymaxTimingMiddleware, TimingInterceptor, UNMATCHED_ROUTE, buildErrorEnvelope, codeForStatus };
|
|
1300
|
-
export type { BuildErrorEnvelopeInput, BymaxCoreModuleOptions, EnvelopeOptions, ErrorDetails, ErrorEnvelope, FilterErrorContext, HealthOptions, ICorrelationIdProvider, ITimingSink, ITraceContextProvider, IUnexpectedErrorSink, MetricsOptions, OpenApiHttpMethod, OpenApiOperationIdFactory, OpenApiOperationKey, OpenApiOptions, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiServerDescriptor, OperationSecurityMap, RequestTimingSample, ResolvedCoreOptions, TelemetryOptions, TimingOptions, TraceContext, UnexpectedErrorReport };
|
|
1341
|
+
export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, BymaxBodyDepthMiddleware, BymaxCoreModule, BymaxExceptionFilter, BymaxTimingMiddleware, TimingInterceptor, UNMATCHED_ROUTE, buildErrorEnvelope, codeForStatus };
|
|
1342
|
+
export type { BodyDepthOptions, BuildErrorEnvelopeInput, BymaxCoreModuleOptions, EnvelopeOptions, ErrorDetails, ErrorEnvelope, FilterErrorContext, HealthOptions, ICorrelationIdProvider, ITimingSink, ITraceContextProvider, IUnexpectedErrorSink, MetricsOptions, OpenApiHttpMethod, OpenApiOperationIdFactory, OpenApiOperationKey, OpenApiOptions, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiServerDescriptor, OperationSecurityMap, RequestTimingSample, ResolvedCoreOptions, TelemetryOptions, TimingOptions, TraceContext, UnexpectedErrorReport };
|