@bymax-one/nest-core 1.6.6 → 1.6.8
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 +114 -1
- package/README.md +554 -898
- package/dist/health/index.d.cts +6 -3
- package/dist/health/index.d.ts +6 -3
- package/dist/http/index.cjs +73 -0
- package/dist/http/index.d.cts +59 -0
- package/dist/http/index.d.ts +59 -0
- package/dist/http/index.mjs +71 -0
- package/dist/index.cjs +206 -79
- package/dist/index.d.cts +62 -9
- package/dist/index.d.ts +62 -9
- package/dist/index.mjs +208 -81
- package/package.json +25 -3
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Injectable, Inject, Catch, Optional, ConfigurableModuleBuilder, Module, BadRequestException, HttpException, Logger, Get, Res, Controller, Req, HttpStatus, UnauthorizedException, NotFoundException } from '@nestjs/common';
|
|
2
2
|
import { HttpAdapterHost, DiscoveryService, Reflector, BaseExceptionFilter, APP_FILTER, DiscoveryModule } from '@nestjs/core';
|
|
3
3
|
import { createHash, timingSafeEqual } from 'crypto';
|
|
4
4
|
import { AsyncResource } from 'async_hooks';
|
|
@@ -16,6 +16,121 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
16
16
|
};
|
|
17
17
|
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
18
18
|
|
|
19
|
+
// src/core.tokens.ts
|
|
20
|
+
var BYMAX_CORE_OPTIONS = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:core-options");
|
|
21
|
+
var BYMAX_CORRELATION_PROVIDER = /* @__PURE__ */ Symbol.for(
|
|
22
|
+
"@bymax-one/nest-core:correlation-provider"
|
|
23
|
+
);
|
|
24
|
+
var BYMAX_TIMING_SINK = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:timing-sink");
|
|
25
|
+
var BYMAX_HEALTH_INDICATORS = /* @__PURE__ */ Symbol.for(
|
|
26
|
+
"@bymax-one/nest-core:health-indicators"
|
|
27
|
+
);
|
|
28
|
+
var BYMAX_HEALTH_TRANSITION_SINK = /* @__PURE__ */ Symbol.for(
|
|
29
|
+
"@bymax-one/nest-core:health-transition-sink"
|
|
30
|
+
);
|
|
31
|
+
var BYMAX_UNEXPECTED_ERROR_SINK = /* @__PURE__ */ Symbol.for(
|
|
32
|
+
"@bymax-one/nest-core:unexpected-error-sink"
|
|
33
|
+
);
|
|
34
|
+
var BYMAX_METRICS_REGISTRY = /* @__PURE__ */ Symbol.for(
|
|
35
|
+
"@bymax-one/nest-core:metrics-registry"
|
|
36
|
+
);
|
|
37
|
+
var BYMAX_TRACE_CONTEXT = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:trace-context");
|
|
38
|
+
|
|
39
|
+
// src/envelope/error-codes.ts
|
|
40
|
+
var BYMAX_BAD_REQUEST = "BYMAX_BAD_REQUEST";
|
|
41
|
+
var BYMAX_VALIDATION_FAILED = "BYMAX_VALIDATION_FAILED";
|
|
42
|
+
var BYMAX_UNAUTHORIZED = "BYMAX_UNAUTHORIZED";
|
|
43
|
+
var BYMAX_FORBIDDEN = "BYMAX_FORBIDDEN";
|
|
44
|
+
var BYMAX_NOT_FOUND = "BYMAX_NOT_FOUND";
|
|
45
|
+
var BYMAX_CONFLICT = "BYMAX_CONFLICT";
|
|
46
|
+
var BYMAX_PAYLOAD_TOO_LARGE = "BYMAX_PAYLOAD_TOO_LARGE";
|
|
47
|
+
var BYMAX_UNSUPPORTED_MEDIA_TYPE = "BYMAX_UNSUPPORTED_MEDIA_TYPE";
|
|
48
|
+
var BYMAX_UNPROCESSABLE_ENTITY = "BYMAX_UNPROCESSABLE_ENTITY";
|
|
49
|
+
var BYMAX_TOO_MANY_REQUESTS = "BYMAX_TOO_MANY_REQUESTS";
|
|
50
|
+
var BYMAX_CLIENT_ERROR = "BYMAX_CLIENT_ERROR";
|
|
51
|
+
var BYMAX_INTERNAL_ERROR = "BYMAX_INTERNAL_ERROR";
|
|
52
|
+
var BYMAX_NOT_IMPLEMENTED = "BYMAX_NOT_IMPLEMENTED";
|
|
53
|
+
var BYMAX_BAD_GATEWAY = "BYMAX_BAD_GATEWAY";
|
|
54
|
+
var BYMAX_SERVICE_UNAVAILABLE = "BYMAX_SERVICE_UNAVAILABLE";
|
|
55
|
+
var BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
|
|
56
|
+
var STATUS_CODES = /* @__PURE__ */ new Map([
|
|
57
|
+
[400, BYMAX_BAD_REQUEST],
|
|
58
|
+
[401, BYMAX_UNAUTHORIZED],
|
|
59
|
+
[403, BYMAX_FORBIDDEN],
|
|
60
|
+
[404, BYMAX_NOT_FOUND],
|
|
61
|
+
[409, BYMAX_CONFLICT],
|
|
62
|
+
[413, BYMAX_PAYLOAD_TOO_LARGE],
|
|
63
|
+
[415, BYMAX_UNSUPPORTED_MEDIA_TYPE],
|
|
64
|
+
[422, BYMAX_UNPROCESSABLE_ENTITY],
|
|
65
|
+
[429, BYMAX_TOO_MANY_REQUESTS],
|
|
66
|
+
[500, BYMAX_INTERNAL_ERROR],
|
|
67
|
+
[501, BYMAX_NOT_IMPLEMENTED],
|
|
68
|
+
[502, BYMAX_BAD_GATEWAY],
|
|
69
|
+
[503, BYMAX_SERVICE_UNAVAILABLE],
|
|
70
|
+
[504, BYMAX_GATEWAY_TIMEOUT]
|
|
71
|
+
]);
|
|
72
|
+
var CLIENT_ERROR_MIN = 400;
|
|
73
|
+
var CLIENT_ERROR_MAX = 500;
|
|
74
|
+
function codeForStatus(status) {
|
|
75
|
+
const catalogued = STATUS_CODES.get(status);
|
|
76
|
+
if (catalogued !== void 0) {
|
|
77
|
+
return catalogued;
|
|
78
|
+
}
|
|
79
|
+
if (status >= CLIENT_ERROR_MIN && status < CLIENT_ERROR_MAX) {
|
|
80
|
+
return BYMAX_CLIENT_ERROR;
|
|
81
|
+
}
|
|
82
|
+
return BYMAX_INTERNAL_ERROR;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/body-depth/body-depth.middleware.ts
|
|
86
|
+
var BODY_TOO_DEEP_MESSAGE = "Request body is nested too deeply";
|
|
87
|
+
function isContainer(node) {
|
|
88
|
+
if (typeof node !== "object" || node === null) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const proto = Object.getPrototypeOf(node);
|
|
92
|
+
return Array.isArray(node) || proto === Object.prototype || proto === null;
|
|
93
|
+
}
|
|
94
|
+
function exceedsDepth(value, maxDepth) {
|
|
95
|
+
let level = [value];
|
|
96
|
+
for (let depth = 0; level.length > 0; depth += 1) {
|
|
97
|
+
const next = [];
|
|
98
|
+
for (const node of level) {
|
|
99
|
+
if (isContainer(node)) {
|
|
100
|
+
if (depth >= maxDepth) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
for (const child of Object.values(node)) {
|
|
104
|
+
next.push(child);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
level = next;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
var BymaxBodyDepthMiddleware = class {
|
|
113
|
+
constructor(options) {
|
|
114
|
+
this.options = options;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @param req - The request, whose `body` the parser has already filled.
|
|
118
|
+
* @param _res - Unused.
|
|
119
|
+
* @param next - Continues the chain.
|
|
120
|
+
* @throws BadRequestException When the body is nested too deeply.
|
|
121
|
+
*/
|
|
122
|
+
use(req, _res, next) {
|
|
123
|
+
if (exceedsDepth(req.body, this.options.bodyDepth.maxDepth)) {
|
|
124
|
+
throw new BadRequestException({ code: BYMAX_BAD_REQUEST, message: BODY_TOO_DEEP_MESSAGE });
|
|
125
|
+
}
|
|
126
|
+
next();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
BymaxBodyDepthMiddleware = __decorateClass([
|
|
130
|
+
Injectable(),
|
|
131
|
+
__decorateParam(0, Inject(BYMAX_CORE_OPTIONS))
|
|
132
|
+
], BymaxBodyDepthMiddleware);
|
|
133
|
+
|
|
19
134
|
// src/route-defaults.ts
|
|
20
135
|
var DEFAULT_HEALTH_PATH = "health";
|
|
21
136
|
var DEFAULT_METRICS_PATH = "metrics";
|
|
@@ -38,6 +153,8 @@ function runtimeEnvironmentName(declared) {
|
|
|
38
153
|
|
|
39
154
|
// src/core.options.ts
|
|
40
155
|
var DEFAULT_INDICATOR_TIMEOUT_MS = 5e3;
|
|
156
|
+
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
157
|
+
var DEFAULT_READINESS_CACHE_MS = 1e3;
|
|
41
158
|
var DEFAULT_OPENAPI_PATH = "docs";
|
|
42
159
|
var DEFAULT_OPENAPI_JSON_PATH = "docs-json";
|
|
43
160
|
var DEFAULT_OPENAPI_TITLE = "API";
|
|
@@ -57,17 +174,48 @@ function resolveEnvelope(raw) {
|
|
|
57
174
|
exposeInternals: raw?.exposeInternals ?? false
|
|
58
175
|
};
|
|
59
176
|
}
|
|
177
|
+
function positiveSafeInteger(name, value, max) {
|
|
178
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
179
|
+
throw new Error(`${name} must be a positive safe integer, received ${String(value)}`);
|
|
180
|
+
}
|
|
181
|
+
if (max !== void 0 && value > max) {
|
|
182
|
+
throw new Error(`${name} must be a positive integer of at most ${max}, received ${value}`);
|
|
183
|
+
}
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
60
186
|
function resolveTiming(raw) {
|
|
61
187
|
const enabled = raw?.enabled ?? true;
|
|
62
188
|
const threshold = raw?.slowRequestThresholdMs;
|
|
63
|
-
return threshold === void 0 ? { enabled } : {
|
|
189
|
+
return threshold === void 0 ? { enabled } : {
|
|
190
|
+
enabled,
|
|
191
|
+
slowRequestThresholdMs: positiveSafeInteger("timing.slowRequestThresholdMs", threshold)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
var DEFAULT_MAX_BODY_DEPTH = 32;
|
|
195
|
+
function resolveBodyDepth(raw) {
|
|
196
|
+
const maxDepth = positiveSafeInteger(
|
|
197
|
+
"bodyDepth.maxDepth",
|
|
198
|
+
raw?.maxDepth ?? DEFAULT_MAX_BODY_DEPTH
|
|
199
|
+
);
|
|
200
|
+
return { enabled: raw?.enabled ?? false, maxDepth };
|
|
64
201
|
}
|
|
65
202
|
function resolveHealth(raw) {
|
|
203
|
+
const readinessCacheMs = raw?.readinessCacheMs ?? DEFAULT_READINESS_CACHE_MS;
|
|
204
|
+
if (!Number.isSafeInteger(readinessCacheMs) || readinessCacheMs < 0) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`health.readinessCacheMs must be a non-negative safe integer, received ${String(readinessCacheMs)}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
66
209
|
return {
|
|
210
|
+
readinessCacheMs,
|
|
67
211
|
enabled: raw?.enabled ?? true,
|
|
68
212
|
path: raw?.path ?? DEFAULT_HEALTH_PATH,
|
|
69
213
|
exposeIndicatorErrors: raw?.exposeIndicatorErrors ?? false,
|
|
70
|
-
indicatorTimeoutMs:
|
|
214
|
+
indicatorTimeoutMs: positiveSafeInteger(
|
|
215
|
+
"health.indicatorTimeoutMs",
|
|
216
|
+
raw?.indicatorTimeoutMs ?? DEFAULT_INDICATOR_TIMEOUT_MS,
|
|
217
|
+
MAX_TIMER_DELAY_MS
|
|
218
|
+
),
|
|
71
219
|
autoDiscover: raw?.autoDiscover ?? false
|
|
72
220
|
};
|
|
73
221
|
}
|
|
@@ -134,6 +282,7 @@ function normalizeCoreOptions(raw) {
|
|
|
134
282
|
return deepFreeze({
|
|
135
283
|
envelope: resolveEnvelope(raw?.envelope),
|
|
136
284
|
timing: resolveTiming(raw?.timing),
|
|
285
|
+
bodyDepth: resolveBodyDepth(raw?.bodyDepth),
|
|
137
286
|
health: resolveHealth(raw?.health),
|
|
138
287
|
metrics: resolveMetrics(raw?.metrics),
|
|
139
288
|
openapi: resolveOpenApi(raw?.openapi, raw?.environment),
|
|
@@ -146,26 +295,6 @@ function normalizeCoreOptions(raw) {
|
|
|
146
295
|
}
|
|
147
296
|
normalizeCoreOptions();
|
|
148
297
|
|
|
149
|
-
// src/core.tokens.ts
|
|
150
|
-
var BYMAX_CORE_OPTIONS = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:core-options");
|
|
151
|
-
var BYMAX_CORRELATION_PROVIDER = /* @__PURE__ */ Symbol.for(
|
|
152
|
-
"@bymax-one/nest-core:correlation-provider"
|
|
153
|
-
);
|
|
154
|
-
var BYMAX_TIMING_SINK = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:timing-sink");
|
|
155
|
-
var BYMAX_HEALTH_INDICATORS = /* @__PURE__ */ Symbol.for(
|
|
156
|
-
"@bymax-one/nest-core:health-indicators"
|
|
157
|
-
);
|
|
158
|
-
var BYMAX_HEALTH_TRANSITION_SINK = /* @__PURE__ */ Symbol.for(
|
|
159
|
-
"@bymax-one/nest-core:health-transition-sink"
|
|
160
|
-
);
|
|
161
|
-
var BYMAX_UNEXPECTED_ERROR_SINK = /* @__PURE__ */ Symbol.for(
|
|
162
|
-
"@bymax-one/nest-core:unexpected-error-sink"
|
|
163
|
-
);
|
|
164
|
-
var BYMAX_METRICS_REGISTRY = /* @__PURE__ */ Symbol.for(
|
|
165
|
-
"@bymax-one/nest-core:metrics-registry"
|
|
166
|
-
);
|
|
167
|
-
var BYMAX_TRACE_CONTEXT = /* @__PURE__ */ Symbol.for("@bymax-one/nest-core:trace-context");
|
|
168
|
-
|
|
169
298
|
// src/optional-peer.ts
|
|
170
299
|
function isMissingModuleError(cause) {
|
|
171
300
|
const code = cause.code;
|
|
@@ -273,52 +402,6 @@ function containRejection(returned, onFailure) {
|
|
|
273
402
|
}
|
|
274
403
|
}
|
|
275
404
|
|
|
276
|
-
// src/envelope/error-codes.ts
|
|
277
|
-
var BYMAX_BAD_REQUEST = "BYMAX_BAD_REQUEST";
|
|
278
|
-
var BYMAX_VALIDATION_FAILED = "BYMAX_VALIDATION_FAILED";
|
|
279
|
-
var BYMAX_UNAUTHORIZED = "BYMAX_UNAUTHORIZED";
|
|
280
|
-
var BYMAX_FORBIDDEN = "BYMAX_FORBIDDEN";
|
|
281
|
-
var BYMAX_NOT_FOUND = "BYMAX_NOT_FOUND";
|
|
282
|
-
var BYMAX_CONFLICT = "BYMAX_CONFLICT";
|
|
283
|
-
var BYMAX_PAYLOAD_TOO_LARGE = "BYMAX_PAYLOAD_TOO_LARGE";
|
|
284
|
-
var BYMAX_UNSUPPORTED_MEDIA_TYPE = "BYMAX_UNSUPPORTED_MEDIA_TYPE";
|
|
285
|
-
var BYMAX_UNPROCESSABLE_ENTITY = "BYMAX_UNPROCESSABLE_ENTITY";
|
|
286
|
-
var BYMAX_TOO_MANY_REQUESTS = "BYMAX_TOO_MANY_REQUESTS";
|
|
287
|
-
var BYMAX_CLIENT_ERROR = "BYMAX_CLIENT_ERROR";
|
|
288
|
-
var BYMAX_INTERNAL_ERROR = "BYMAX_INTERNAL_ERROR";
|
|
289
|
-
var BYMAX_NOT_IMPLEMENTED = "BYMAX_NOT_IMPLEMENTED";
|
|
290
|
-
var BYMAX_BAD_GATEWAY = "BYMAX_BAD_GATEWAY";
|
|
291
|
-
var BYMAX_SERVICE_UNAVAILABLE = "BYMAX_SERVICE_UNAVAILABLE";
|
|
292
|
-
var BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
|
|
293
|
-
var STATUS_CODES = /* @__PURE__ */ new Map([
|
|
294
|
-
[400, BYMAX_BAD_REQUEST],
|
|
295
|
-
[401, BYMAX_UNAUTHORIZED],
|
|
296
|
-
[403, BYMAX_FORBIDDEN],
|
|
297
|
-
[404, BYMAX_NOT_FOUND],
|
|
298
|
-
[409, BYMAX_CONFLICT],
|
|
299
|
-
[413, BYMAX_PAYLOAD_TOO_LARGE],
|
|
300
|
-
[415, BYMAX_UNSUPPORTED_MEDIA_TYPE],
|
|
301
|
-
[422, BYMAX_UNPROCESSABLE_ENTITY],
|
|
302
|
-
[429, BYMAX_TOO_MANY_REQUESTS],
|
|
303
|
-
[500, BYMAX_INTERNAL_ERROR],
|
|
304
|
-
[501, BYMAX_NOT_IMPLEMENTED],
|
|
305
|
-
[502, BYMAX_BAD_GATEWAY],
|
|
306
|
-
[503, BYMAX_SERVICE_UNAVAILABLE],
|
|
307
|
-
[504, BYMAX_GATEWAY_TIMEOUT]
|
|
308
|
-
]);
|
|
309
|
-
var CLIENT_ERROR_MIN = 400;
|
|
310
|
-
var CLIENT_ERROR_MAX = 500;
|
|
311
|
-
function codeForStatus(status) {
|
|
312
|
-
const catalogued = STATUS_CODES.get(status);
|
|
313
|
-
if (catalogued !== void 0) {
|
|
314
|
-
return catalogued;
|
|
315
|
-
}
|
|
316
|
-
if (status >= CLIENT_ERROR_MIN && status < CLIENT_ERROR_MAX) {
|
|
317
|
-
return BYMAX_CLIENT_ERROR;
|
|
318
|
-
}
|
|
319
|
-
return BYMAX_INTERNAL_ERROR;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
405
|
// src/envelope/error-envelope.ts
|
|
323
406
|
function buildErrorEnvelope(input) {
|
|
324
407
|
const base = {
|
|
@@ -791,10 +874,12 @@ function summarizeRejection(reason) {
|
|
|
791
874
|
}
|
|
792
875
|
return `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH - TRUNCATION_ELLIPSIS.length)}${TRUNCATION_ELLIPSIS}`;
|
|
793
876
|
}
|
|
794
|
-
|
|
877
|
+
function startTimeout(indicator, timeoutMs) {
|
|
878
|
+
const controller = new AbortController();
|
|
795
879
|
let timer;
|
|
796
880
|
const timedOut = new Promise((resolve) => {
|
|
797
881
|
timer = setTimeout(() => {
|
|
882
|
+
controller.abort();
|
|
798
883
|
resolve({
|
|
799
884
|
entry: { name: indicator.name, status: "down", details: { timedOutAfterMs: timeoutMs } },
|
|
800
885
|
isUp: false,
|
|
@@ -802,8 +887,12 @@ async function runIndicator(indicator, timeoutMs, exposeErrors) {
|
|
|
802
887
|
});
|
|
803
888
|
}, timeoutMs);
|
|
804
889
|
});
|
|
805
|
-
|
|
806
|
-
|
|
890
|
+
return { signal: controller.signal, timedOut, clear: () => clearTimeout(timer) };
|
|
891
|
+
}
|
|
892
|
+
async function runIndicator(indicator, timeoutMs, exposeErrors) {
|
|
893
|
+
const { signal, timedOut, clear } = startTimeout(indicator, timeoutMs);
|
|
894
|
+
const checked = Promise.resolve().then(() => indicator.check(signal)).then((result) => {
|
|
895
|
+
const entry = exposeErrors ? { ...result, name: indicator.name } : { name: indicator.name, status: result.status };
|
|
807
896
|
return result.status === "up" ? { entry, isUp: true } : { entry, isUp: false, cause: { kind: "reported-down" } };
|
|
808
897
|
}).catch((reason) => {
|
|
809
898
|
const message = summarizeRejection(reason);
|
|
@@ -816,7 +905,7 @@ async function runIndicator(indicator, timeoutMs, exposeErrors) {
|
|
|
816
905
|
try {
|
|
817
906
|
return await Promise.race([checked, timedOut]);
|
|
818
907
|
} finally {
|
|
819
|
-
|
|
908
|
+
clear();
|
|
820
909
|
}
|
|
821
910
|
}
|
|
822
911
|
function describeCause(cause) {
|
|
@@ -876,6 +965,8 @@ var HealthService = class {
|
|
|
876
965
|
* Comparing states alone would then let the stale observation win.
|
|
877
966
|
*/
|
|
878
967
|
this.probeSequence = 0;
|
|
968
|
+
/** Set once shutdown begins, after which readiness answers not ready without running anything. */
|
|
969
|
+
this.draining = false;
|
|
879
970
|
}
|
|
880
971
|
/**
|
|
881
972
|
* Report one check's current state, emitting only when it differs from the
|
|
@@ -1048,6 +1139,14 @@ var HealthService = class {
|
|
|
1048
1139
|
);
|
|
1049
1140
|
return this.effectiveIndicators;
|
|
1050
1141
|
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Stop reporting ready the moment shutdown begins, so a load balancer stops routing here while
|
|
1144
|
+
* the process drains. Nothing cached or in progress is consulted again: every later probe is answered
|
|
1145
|
+
* before either is read.
|
|
1146
|
+
*/
|
|
1147
|
+
beforeApplicationShutdown() {
|
|
1148
|
+
this.draining = true;
|
|
1149
|
+
}
|
|
1051
1150
|
/**
|
|
1052
1151
|
* Liveness check: the process is up and able to respond. Runs no
|
|
1053
1152
|
* indicators, so it never depends on the health of anything else.
|
|
@@ -1062,9 +1161,33 @@ var HealthService = class {
|
|
|
1062
1161
|
* aggregate the results. `status` is `'ok'` only when every indicator
|
|
1063
1162
|
* reports `up`; an empty indicator list is vacuously `'ok'`.
|
|
1064
1163
|
*
|
|
1164
|
+
* Probes share the work: one that arrives while a run is in progress awaits it, and for
|
|
1165
|
+
* `health.readinessCacheMs` after it ends the answer is returned without running anything.
|
|
1166
|
+
* The endpoint is usually unauthenticated, so probe volume must not set dependency volume.
|
|
1167
|
+
* Once shutdown has begun the answer is always not ready. The returned object is shared between
|
|
1168
|
+
* callers and must not be mutated.
|
|
1169
|
+
*
|
|
1170
|
+
* @returns The aggregated health response.
|
|
1171
|
+
*/
|
|
1172
|
+
checkReadiness() {
|
|
1173
|
+
if (this.draining) {
|
|
1174
|
+
return Promise.resolve({ status: "error", checks: [{ name: "shutdown", status: "down" }] });
|
|
1175
|
+
}
|
|
1176
|
+
const cached = this.cached;
|
|
1177
|
+
if (cached !== void 0 && performance.now() - cached.at < this.options.health.readinessCacheMs) {
|
|
1178
|
+
return Promise.resolve(cached.response);
|
|
1179
|
+
}
|
|
1180
|
+
this.inflight ??= this.runReadiness().finally(() => {
|
|
1181
|
+
this.inflight = void 0;
|
|
1182
|
+
});
|
|
1183
|
+
return this.inflight;
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Run every indicator once and record the answer.
|
|
1187
|
+
*
|
|
1065
1188
|
* @returns The aggregated health response.
|
|
1066
1189
|
*/
|
|
1067
|
-
async
|
|
1190
|
+
async runReadiness() {
|
|
1068
1191
|
const timeoutMs = this.options.health.indicatorTimeoutMs;
|
|
1069
1192
|
const exposeErrors = this.options.health.exposeIndicatorErrors;
|
|
1070
1193
|
const seq = ++this.probeSequence;
|
|
@@ -1076,7 +1199,9 @@ var HealthService = class {
|
|
|
1076
1199
|
}
|
|
1077
1200
|
const checks = outcomes.map((outcome) => outcome.entry);
|
|
1078
1201
|
const status = checks.every((check) => check.status === "up") ? "ok" : "error";
|
|
1079
|
-
|
|
1202
|
+
const response = { status, checks };
|
|
1203
|
+
this.cached = { at: performance.now(), response };
|
|
1204
|
+
return response;
|
|
1080
1205
|
}
|
|
1081
1206
|
};
|
|
1082
1207
|
HealthService = __decorateClass([
|
|
@@ -1695,12 +1820,14 @@ var BymaxCoreModule = class extends BymaxCoreModuleBase {
|
|
|
1695
1820
|
* @param consumer - Nest's middleware consumer.
|
|
1696
1821
|
*/
|
|
1697
1822
|
configure(consumer) {
|
|
1698
|
-
if (!this.resolved.timing.enabled) {
|
|
1699
|
-
return;
|
|
1700
|
-
}
|
|
1701
1823
|
const adapter = this.adapterHost?.httpAdapter;
|
|
1702
|
-
|
|
1703
|
-
|
|
1824
|
+
if (this.resolved.timing.enabled) {
|
|
1825
|
+
const onFastify = bridgeFastifyRouteMetadata(adapter);
|
|
1826
|
+
consumer.apply(BymaxTimingMiddleware).forRoutes(onFastify ? "{*splat}" : "/");
|
|
1827
|
+
}
|
|
1828
|
+
if (this.resolved.bodyDepth.enabled && adapter?.getType?.() !== "fastify") {
|
|
1829
|
+
consumer.apply(BymaxBodyDepthMiddleware).forRoutes("/");
|
|
1830
|
+
}
|
|
1704
1831
|
}
|
|
1705
1832
|
/**
|
|
1706
1833
|
* Register the module synchronously. Options are known now, so disabled
|
|
@@ -1892,4 +2019,4 @@ TimingInterceptor = __decorateClass([
|
|
|
1892
2019
|
__decorateParam(3, Inject(BYMAX_TRACE_CONTEXT))
|
|
1893
2020
|
], TimingInterceptor);
|
|
1894
2021
|
|
|
1895
|
-
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 };
|
|
2022
|
+
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-core",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
4
4
|
"description": "Zero-dependency NestJS 11 application foundation kit: error-envelope exception filter, request-timing middleware, pagination helpers, health endpoints with indicator discovery, an optional Prometheus metrics endpoint with a contribution contract, OpenAPI documents in development, and OpenTelemetry trace correlation.",
|
|
5
5
|
"author": "Bymax One <support@bymax.one>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,6 +63,16 @@
|
|
|
63
63
|
"default": "./dist/metrics/index.cjs"
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
+
"./http": {
|
|
67
|
+
"import": {
|
|
68
|
+
"types": "./dist/http/index.d.ts",
|
|
69
|
+
"default": "./dist/http/index.mjs"
|
|
70
|
+
},
|
|
71
|
+
"require": {
|
|
72
|
+
"types": "./dist/http/index.d.cts",
|
|
73
|
+
"default": "./dist/http/index.cjs"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
66
76
|
"./openapi": {
|
|
67
77
|
"import": {
|
|
68
78
|
"types": "./dist/openapi/index.d.ts",
|
|
@@ -77,6 +87,7 @@
|
|
|
77
87
|
},
|
|
78
88
|
"scripts": {
|
|
79
89
|
"build": "pnpm clean && tsup",
|
|
90
|
+
"check:file-length": "node scripts/check-file-length.mjs",
|
|
80
91
|
"check:exports": "attw --pack . --profile strict",
|
|
81
92
|
"check:mutants": "node scripts/check-mutation-directives.mjs",
|
|
82
93
|
"check:published": "node scripts/check-published-surface.mjs",
|
|
@@ -94,8 +105,8 @@
|
|
|
94
105
|
"size": "node scripts/check-size.mjs",
|
|
95
106
|
"test": "jest",
|
|
96
107
|
"test:cov": "jest --coverage",
|
|
97
|
-
"test:cov:all": "jest --config jest.coverage.config.ts --coverage",
|
|
98
|
-
"test:e2e": "jest --config jest.e2e.config.ts",
|
|
108
|
+
"test:cov:all": "node --experimental-vm-modules --disable-warning=ExperimentalWarning node_modules/jest/bin/jest.js --config jest.coverage.config.ts --coverage",
|
|
109
|
+
"test:e2e": "node --experimental-vm-modules --disable-warning=ExperimentalWarning node_modules/jest/bin/jest.js --config jest.e2e.config.ts",
|
|
99
110
|
"test:watch": "jest --watch",
|
|
100
111
|
"typecheck": "tsc --noEmit"
|
|
101
112
|
},
|
|
@@ -113,6 +124,7 @@
|
|
|
113
124
|
"@nestjs/core": "^11.1.18",
|
|
114
125
|
"@nestjs/swagger": "^11.0.0",
|
|
115
126
|
"@opentelemetry/api": "^1.9.0",
|
|
127
|
+
"helmet": "^8.0.0",
|
|
116
128
|
"prom-client": "^15.0.0",
|
|
117
129
|
"reflect-metadata": "^0.2.0",
|
|
118
130
|
"rxjs": "^7.0.0"
|
|
@@ -124,6 +136,9 @@
|
|
|
124
136
|
"@opentelemetry/api": {
|
|
125
137
|
"optional": true
|
|
126
138
|
},
|
|
139
|
+
"helmet": {
|
|
140
|
+
"optional": true
|
|
141
|
+
},
|
|
127
142
|
"prom-client": {
|
|
128
143
|
"optional": true
|
|
129
144
|
}
|
|
@@ -150,6 +165,8 @@
|
|
|
150
165
|
"@types/supertest": "^7.2.0",
|
|
151
166
|
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
|
152
167
|
"@typescript-eslint/parser": "^8.59.3",
|
|
168
|
+
"class-transformer": "^0.5.1",
|
|
169
|
+
"class-validator": "^0.15.1",
|
|
153
170
|
"eslint": "^9.39.4",
|
|
154
171
|
"eslint-config-prettier": "^10.1.8",
|
|
155
172
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
@@ -158,6 +175,7 @@
|
|
|
158
175
|
"eslint-plugin-security": "^4.0.0",
|
|
159
176
|
"fastify": "^5.12.5",
|
|
160
177
|
"globals": "^17.6.0",
|
|
178
|
+
"helmet": "^8.3.0",
|
|
161
179
|
"husky": "^9.1.7",
|
|
162
180
|
"jest": "^30.4.2",
|
|
163
181
|
"lint-staged": "^17.2.0",
|
|
@@ -182,6 +200,7 @@
|
|
|
182
200
|
"metrics",
|
|
183
201
|
"openapi",
|
|
184
202
|
"swagger",
|
|
203
|
+
"helmet",
|
|
185
204
|
"opentelemetry",
|
|
186
205
|
"tracing",
|
|
187
206
|
"typescript"
|
|
@@ -209,6 +228,9 @@
|
|
|
209
228
|
],
|
|
210
229
|
"metrics": [
|
|
211
230
|
"./dist/metrics/index.d.cts"
|
|
231
|
+
],
|
|
232
|
+
"http": [
|
|
233
|
+
"./dist/http/index.d.cts"
|
|
212
234
|
]
|
|
213
235
|
}
|
|
214
236
|
}
|